• 原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:

  • function Man() {};
        Man.prototype = {
           constructor: Man,
           name: 'Jess',
           age: 25,
           family: ['a', 'b', 'c'],                   //添加了一个数组属性
           run: function () {
                  return this.name + this.age +this.family;
           }
    };
     
    var obj1 = new Man();
    obj1.family.push('d');                                  //在实例中添加 d
    alert(obj1.run());
     
    var obj2 = new Man();
    alert(obj2.run());                                 //共享带来的麻烦,也有 d 了
    //数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要保留自己的特性,而不能共享。
  • 组合构造函数+原型模式

  • function Man(name, age) {                           //不共享的使用构造函数
           this.name= name;
           this.age= age;
           this.family = ['a', 'b', 'c'];
    };
    Man.prototype = {                                   //共享的使用原型模式
           constructor: Man,
           run: function () {
                  return this.name + this.age +this.family;
           }
    };
    //在声明一个对象时,构造函数+原型部分让人感觉很怪异
  • 动态原型模式

  • function Man(name ,age) {                           //将所有信息封装到函数体内
           this.name= name;
           this.age= age;
           
           if(typeof this.run != 'function') {          //仅在第一次调用的初始化
                  Man.prototype.run = function () {
                         returnthis.name + this.age + 'running...';
                  };
           }
    }
     
    var obj = new Man('Jess', 25);
    alert(obj.run());
    /*
    当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。
    */
    if (typeof this.run != 'function') {
        alert('第一次初始化');                               //调试用
        Man.prototype.run= function () {
            return this.name + this.age + 'running...';
        };
    }
     
    var obj = new Man('Jess', 25);                          //第一次创建对象
    alert(obj.run());                                       //第一次调用
    alert(obj.run());                                       //第二次调用
     
    var obj2 = new Man('Jack', 28);                         //第二次创建对象
    alert(obj2.run());
    alert(obj2.run());
    /*
    使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。
    */
  • 寄生构造函数

  • function Man(name, age) {
           var obj = new Object();
           obj.name= name;
           obj.age= age;
           obj.run= function () {
                  return this.name + this.age + 'running...';
           };
           return obj;
    }
    /*
    寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
    */
    //String.prototype.addstring可以通过寄生构造的方式添加
    function myString(string) {                               
           varstr = new String(string);
           str.addstring= function () {
                  return this + ',hello!';
           };
           returnstr;
    }
     
    var str = new myString('Jess');                     //比直接在引用原型添加要繁琐好多
    alert(str.addstring());
  • 稳妥构造函数 在一些安全的环境中,比如禁止使用thisnew,这里的this是构造函数里不使用this,这里的new是在外部实例化构造函数时不使用new。

  • function Man(name , age) {
           var obj = new Object();
           obj.run= function () {
                  return name + age + 'running...';           //直接打印参数即可
           };
           return obj;
    }
     
    var obj = Man('Jess', 25);                                 //直接调用函数
    alert(obj.run());
     
    //稳妥构造函数和寄生类似。
  • 继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。wKioL1Q2HYKTUIaWAAFyXjo1DuE514.jpg

  • function Box() {                               //Box构造
           this.name= 'Lee';
    }
     
    function Desk() {                              //Desk构造
           this.age= 100;
    }
     
    Desk.prototype = new Box();                    //Desc继承了Box,通过原型,形成链条
     
    var desk = new Desk();
    alert(desk.age);
    alert(desk.name);                               //得到被继承的属性
     
    function Table() {                               //Table构造
    this.level = 'AAAAA';
    }                                               
     
    Table.prototype = new Desk();                    //继续原型链继承
     
    var table = new Table();
    alert(table.name);                                //继承了Box和Desk
    /*
    以上原型链继承还缺少一环,那就是Obejct,所有的构造函数都继承自Obejct。而继承Object是自动完成的,并不需要程序员手动继承。
    */
    alert(table instanceof Object);                    //true
    alert(desk instanceof Table);                      //false,desk是table的超类
    alert(table instanceof Desk);                      //true
    alert(table instanceof Box);                       //true
  • 被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。为了解决引用共享和超类型无法传参的问题,采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。

  • 组合继承 = 原型链+借用构造函数

  • function Box(age) {
           this.name= ['Lee', 'Jack', 'Jess']
           this.age= age;
    }
     
    Box.prototype.run = function () {                      
           return this.name + this.age;
    };
     
    function Desk(age) {
           Box.call(this,age);                              //对象冒充
    }
     
    Desk.prototype = new Box();                             //原型链继承
     
    var desk = new Desk(30);
    alert(desk.run());
  • 原型式继承 借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。

  • function obj(o) {                                 //传递一个字面量函数
           functionF() {}                             //创建一个构造函数
           F.prototype= o;                            //把字面量函数赋值给构造函数的原型
           returnnew F();                             //最终返回出实例化的构造函数
    }
     
    var box = {                                       //字面量对象
           name: 'Lee',
           arr: ['a','b','c']
    };
     
    var box1 = obj(box);                              //传递
    alert(box1.name);
    box1.name = 'Jack';
    alert(box1.name);
     
    alert(box1.arr);
    box1.arr.push('d');
    alert(box1.arr);
     
    var box2 = obj(box);                              //传递
    alert(box2.name);
    alert(box2.arr);                                  //引用类型共享了
  • 寄生式继承把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。

  • function Box(name) {
           this.name= name;
           this.arr= ['a','b','c'];
    }
     
    Box.prototype.run = function () {
           returnthis.name;
    };
     
    function Desk(name, age) {
           Box.call(this,name);                                 //第二次调用Box
           this.age= age;
    }
     
    Desk.prototype = new Box();                             //第一次调用Box
  • 寄生组合继承

  • function obj(o) {
           functionF() {}
           F.prototype= o;
           returnnew F();
    }
     
    function create(box, desk) {
           varf = obj(box.prototype);
           f.constructor= desk;
           desk.prototype= f;
    }
     
    function Box(name) {
           this.name= name;
           this.arr= ['a','b','c'];
    }
     
    Box.prototype.run = function () {
           returnthis.name;
    };
     
    function Desk(name, age) {
           Box.call(this,name);
           this.age= age;
    }
     
    inPrototype(Box, Desk);                                    //通过这里实现继承
     
    var desk = new Desk('Jess',25);
    desk.arr.push('e');
    alert(desk.arr);
    alert(desk.run());                                         //只共享了方法
     
    var desk2 = new Desk('Jack', 28);
    alert(desk2.arr);                                          //引用问题解决