zoukankan      html  css  js  c++  java
  • (79)Wangdao.com第十五天_JavaScript 对象的继承_prototype原型对象_封装_函数式编程

    javascript 内置了许多 function 函数(){...}

    js 执行首先就会执行自己内置的函数定义 (function Function、function Object)

    对象的继承

    大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。

    传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,称之为 JavaScript 的原型链继承

    JavaScript 继承机制的设计思想就是,原型对象 prototype 的所有属性和方法,都能被实例对象共享

    ES6 引入了 class 语法

    • 构造函数的缺点
      • 同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费
        • function Cat(name, color) {
              this.name = name;
              this.color = color;
              this.bar = function () {
                  console.log('喵喵');
              };
          }
          
          var cat1 = new Cat('大毛', '白色');
          var cat2 = new Cat('二毛', '黑色');
          
          cat1.meow === cat2.meow    // false

          cat1 和 cat2 是同一个构造函数的两个实例,它们都具有 bar 方法。由于 bar 方法是生成在每个实例对象上面,所以两个实例就生成了两次。

      • 也就是说,每新建一个实例,就会新建一个 bar方法。这既没有必要,又浪费系统资源,因为所有 bar 方法都是同样的行为,完全应该共享
      • 这个问题的解决方法,就是 JavaScript 的原型对象(prototype)
    • JavaScript 的原型对象
      • JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享
      • 属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系
      • JavaScript 规定,每个函数都有一个 prototype 属性,指向一个对象。
        • 对于普通函数来说,该属性基本无用。
        • 但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
          • function Animal(name) {
                this.name = name;
            }
            Animal.prototype.color = 'white';
            
            var cat1 = new Animal('大毛');
            var cat2 = new Animal('二毛');
            
            cat1.color    // 'white'
            cat2.color    // 'white'
        • 原型对象的作用,就是定义所有实例对象共享的属性和方法。
    • 在函数创建时,都会默认创建 显示原型对象

     

    读取属性/方法,沿着原型链找

    设置属性/ 方法,只会查看和影响自身

    所有函数都具有 prototype 显式原型属性,指向一个对象____原型对象 

    •  function Dog(){};

    所有对象都是某个构造函数的实例,都拥有 __proto__隐式原型属性

    •  var wangCai = new Dog("旺财", "2");

    注意:

    • 由于 函数 也是一个对象,所以 函数 也拥有一个 __proto__隐式原型属性,由原生底层语言实现

    constructor    构造函数____等于函数本身

    __proto__    隐式原型属性____指向 该对象的构造函数的原型对象 prototype 隐式原型属性 指向 上一级对象的原型对象) 

    • 同一构造函数的 所有实例对象 都有一个 隐式原型 指向同一个原型对象

    • 也就是说,可以在 构造函数 定义时,存放一些实例对象 公共方法 公共属性

     

    • 底层 native code 用 c/c++ 实现
    • 所有 函数 有一个 prototype 显示原型属性

    所有函数都是 function Function(){...} 的实例

    • 所有 实例对象 都有一个 __proto__ 隐式原型属性 (包括原型对象都有一个 __proto__ )
    • 所有 原型对象 都有 constructor 属性指向 构造函数

     

      • 原型链
        • 当调用某对象的属性或者方法时,沿着 __proto__ 这条链向上查找,
            • 首先在自身作用域中寻找,
            • 然后到原型对象中寻找,
            • 再到原型对象的原型对象中寻找,直到找到 Object 。
            • 如果始终没找到就返回 undefined。
        • 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性____这叫做 “覆盖”(overriding)
        • JavaScript 规定,所有对象都有自己的 隐式原型属性 __proto__        指向 new 构造函数的 原型对象
          • 一方面,任何一个对象,都可以充当其他对象的原型;
          • 另一方面,由于原型对象也是对象,所以它也有自己的原型。
          • 因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
        • Object.prototype 的原型是 null ,因此,原型链的尽头就是 null 
        • 所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
            • /*
                  面试题
              */
              var A = function() {
              
              }
              
              A.prototype.n = 1
              
              var b = new A()
              
              A.prototype = {    // 改变的 只是一个地址值,而不会改变 真正对象 的存在(b 始终指向那个 原始的 prototype 对象)
                  n: 2,
                  m: 3
              }
              
              var c = new A()
              console.log(b.n, b.m, c.n, c.m)
        • 举例来说,如果让构造函数的 prototype 属性指向一个数组,就意味着实例对象可以调用数组方法
          • prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数
          • constructor 属性的作用
            • 可以得知某个实例对象,到底是哪一个构造函数产生的
            • 有了 constructor 属性,就可以从一个实例对象新建另一个实例
              • function Constr() {}
                var x = new Constr();
                
                var y = new x.constructor();
                y instanceof Constr;    // true

                这使得在实例方法中,调用自身的构造函数成为可能

                • Constr.prototype.createCopy = function () {
                      return new this.constructor();
                  };
        • constructor 属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改 constructor 属性,防止引用的时候出错
        • 要么将 constructor 属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证 instanceof 运算符不会失真
        • 如果不能确定 constructor 属性是什么函数,还有一个办法:通过 name 属性,从实例得到构造函数的名称。
          • function Foo() {}
            var f = new Foo();
            f.constructor.name    // "Foo"
      • 所有 函数 都是 function Function(){} 的实例对象,

          • 包括 Object 的构造函数        的 __proto__ 都指向 Function 的原型对象

          • 甚至 Function 自己的构造函数        的 __proto__ 都指向 Function 的原型对象
              • console.log(Object instanceof Function);    // true
                console.log(Object instanceof Object);    // true
                console.log(Function instanceof Function);    // true
                console.log(Function instanceof Object);    // true
      • instanceOf 运算符
            • A instanceof B            如果 B 函数的显示原型对象 A 对象的 __proto__ 原型链上,则返回 true,否则返回 false
        • 返回一个布尔值,表示对象是否为某个构造函数的实例
          • var v = new Vehicle();
            v instanceof Vehicle;    // true

            实例对象 instanceOf 构造函数

            • 它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上
              v instanceof Vehicle
              // 等同于
              Vehicle.prototype.isPrototypeOf(v)
        • 有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof 判断会失真
          • var obj = Object.create(null);
            typeof obj;    // "object"
            Object.create(null) instanceof Object;    // false

            因此,只要一个对象的原型不是null,instanceof运算符的判断就不会失真。

        • 使用 instanceOf 判断一个变量的类型
          • var x = [1, 2, 3];
            var y = {};
            x instanceof Array    // true
            y instanceof Object    // true

            注意,instanceof 运算符只能用于对象,不适用原始类型的值。

        • 对于undefined和null,instanceOf运算符总是返回false
        • 利用 instanceof 运算符,还可以巧妙地解决,调用构造函数时,忘了加 new 命令的问题
          • function Fubar (foo, bar) {
                if (this instanceof Fubar) {
                    this._foo = foo;
                    this._bar = bar;
                } else {
                    return new Fubar(foo, bar);
                }
            }
      • 构造函数的继承

    让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现

    • 在子类的构造函数中,调用父类的构造函数
    • 让子类的原型指向父类的原型,这样子类就可以继承父类原型
      • function Sub(value) {
            Super.call(this);
            Sub.prototype = Object.create(Super.prototype);
            Sub.prototype.constructor = Sub;
        
            this.name = value;
            Sub.prototype.method = '...';
        }

        另外一种写法是 Sub.prototype 等于一个父类实例,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法

        • Sub.prototype = new Super();
    • 有时只需要单个方法的继承,这时可以采用下面的写法
      • ClassB.prototype.print = function() {
            ClassA.prototype.print.call(this);
            // some code
        }

        这就等于继承了父类A的 print 方法

     

    • 封装——函数式编程
    • 在 ES6 加入 class 关键字之前的编程方式
    • /**** 旨在实现封装的前提下,最少占用内存 ****/
      // 封装 父类
      function Parent(name, age){
          this.name = name;
          this.age = age;
      };
      
      Parent.prototype.setName = function(name){
          this.name = name;
      };
      
      Parent.prototype.setAge = function(age){
          this.age = age;
      };
      
      // 封装 子类
      function Child(name, age){
          Parent.call(this, name, age);    // 继承父类的属性
          this.isCrying = false;
      };
      
      Child.prototype = new Parent();    // 继承父类的方法
      Child.prototype.constructor = Child;    // 修正构造器指向

     

      • 多重继承
        • JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能
          • function M1() {
                this.hello = 'hello';
            }

            function M2() {
                this.world = 'world';
            }

            function S() {
                M1.call(this);
                // 继承 M1
                S.prototype = Object.create(M1.prototype);

                M2.call(this);
                // 继承链上加入 M2
                Object.assign(S.prototype, M2.prototype);

                // 指定构造函数
                S.prototype.constructor = S;
            }

            var s = new S();
            s.hello    // 'hello'
            s.world    // 'world'

            子类 S 同时继承了父类 M1 和 M2 。这种模式又称为 Mixin(混入)

     

      • 模块
        • JavaScript 不是一种模块化编程语言,ES6 才开始支持 “类” 和 “模块”
        • ES5 中传统方法实现模块
          • 模块是实现特定功能的一组属性和方法的封装
            • 简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面
            • 但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
              • var module1 = new Object({
                   _count : 0,
                   m1 : function (){
                      //...
                   },
                   m2 : function (){
                       //...
                   }
                });
          • 可以利用构造函数,封装私有变量
            • function StringBuilder() {
                  var buffer = [];
              
                  this.add = function (str) {
                       buffer.push(str);
                  };
              
                  this.toString = function () {
                      return buffer.join('');
                  };
              }
            • 构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据
            • 背了构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不应该保存在实例对象以外。同时,非常耗费内存)
            • 解决:
              • function StringBuilder() {
                    this._buffer = [];
                    StringBuilder.prototype = {
                        constructor: StringBuilder,
                        add: function (str) {
                            this._buffer.push(str);
                        },
                        toString: function () {
                            return this._buffer.join('');
                        }
                    };
                }

                以上代码将私有变量放入实例对象中,好处是看上去更自然,但是它的私有变量可以从外部读写,不是很安全

          • 使用 “立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的
              • var module1 = (function () {
                   var age = 0;
                   var getAge = function () {
                       return this.age;
                   };
                   var setAge = function (age) {
                      this.age = age;
                   };
                   return {
                      getAge : getAge,
                      setAge : setAge
                   };
                })();

                使用上面的写法,外部代码无法直接获取内部的_count变量。

          • 模块的 放大模式 (argumentation)
            • 如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”。
              • var module1 = (function (mod){
                   mod.m3 = function () {
                      //...
                   };
                   return mod;
                })(module1);

                上面的代码为 module1 模块添加了一个新方法 m3(),然后返回新的 module1 模块。

            • 在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。
            • 如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用 "宽放大模式"(Loose augmentation)
              • var module1 = (function (mod) {
                    //...
                    return mod;
                })(window.module1 || {});

                与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象

          • 输入全局变量
            • 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互
            • 为了在模块内部调用全局变量,必须显式地将其他变量输入模块
              • var module1 = (function ($, YAHOO) {
                   //...
                })(jQuery, YAHOO);
              • 上面的module1模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入module1

              • 这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显

            • 立即执行函数还可以起到命名空间的作用
              • (function($, window, document) {
                
                    function go(num) {
                    }
                
                    function handleEvents() {
                    }
                
                    function initialize() {
                    }
                
                    function dieCarouselDie() {
                    }
                
                    //attach to the global scope
                    window.finalCarousel = {
                        init : initialize,
                        destroy : dieCouraselDie
                    }
                
                })( jQuery, window, document );
              • 上面代码中,finalCarousel 对象输出到全局,对外暴露 init() 和 destroy() 接口,

              • 内部方法 go()、handleEvents()、initialize()、dieCarouselDie() 都是外部无法调用的。

    --------小尾巴 ________一个人欣赏-最后一朵颜色的消逝-忠诚于我的是·一颗叫做野的心.决不受人奴役.怒火中生的那一刻·终将结束...
  • 相关阅读:
    函数和函数模版在一个。cpp中的情况!(除了左移和右移,其他的不要用友元函数!!!)
    const typedef 和指针的问题(这里必须初始化的才初始化了,不必须的则没有初始化)
    const constptr 和引用的盲点(未解决)
    对于数据流建模和行为级建模的梳理(重点)
    vivado实现模16的计数器
    用vivado实现4比特加法器
    三输入或门(发现这个玩意很不好耍,编程出现错误,不知道哪里出现的,一不小心2输成3也无法查证)
    SpringMVC第一个例子
    Mybatis与Spring的mapper代理整合方法
    Mybatis与Spring的原生Dao整合
  • 原文地址:https://www.cnblogs.com/tianxiaxuange/p/9800827.html
Copyright © 2011-2022 走看看