原型链继承

    function Parent() {
      this.name = 'zhangsan';
      this.children = ['A', 'B', 'C'];
    }
    Parent.prototype.getName = function() {
      console.log(this.name);
    }

    function Child() {

    }
    Child.prototype = new Parent();
    var child = new Child();
    console.log(child.getName());

借用构造函数(经典继承)

    function Parent(age) {
      this.names = ['zhangsan', 'lisi'];
      this.age = age;

      this.getName = function() {
        return this.names;
      }

      this.getAge = function() {
        return this.age;
      }
    }

    function Child(age) {
      Parent.call(this, age);
    }
    var child = new Child(18);
    child.names.push('haha');
    console.log(child.names);

    var child2 = new Child(20);
    child2.names.push('yaya');
    console.log(child2.names);

组合继承(原型链继承和经典继承双剑合璧)

    /**
    * 父类构造函数
    * @param name
    * @constructor
    */
    function Parent(name) {
      this.name = name;
      this.colors = ['red', 'green', 'blue'];
    }

    Parent.prototype.getName = function() {
      console.log(this.name);
    }

    // child
    function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
    }

    Child.prototype = new Parent();
    // 校正child的构造函数
    Child.prototype.constructor = Child;

    // 创建实例
    var child1 = new Child('zhangsan', 18);
    child1.colors.push('orange');
    console.log(child1.name, child1.age, child1.colors);    // zhangsan 18 (4) ["red", "green", "blue", "orange"]

    var child2 = new Child('lisi', 28);
    console.log(child2.name, child2.age, child2.colors);    // lisi 28 (3) ["red", "green", "blue"]

------ 高级继承的实现

原型式继承

    function createObj(o) {
      function F(){};
      // 关键:将传入的对象作为创建对象的原型
      F.prototype = o;
      return new F();
    }

    // test
    var person = {
        name: 'zhangsan',
        friends: ['lisi', 'wangwu']
    }
    var person1 = createObj(person);
    var person2 = createObj(person);

    person1.name = 'wangdachui';
    console.log(person1.name, person2.name);  // wangdachui, zhangsan

    person1.friends.push('songxiaobao');
    console.log(person2.friends);       // lisi wangwu songxiaobao

寄生式继承

    // 创建一个用于封装继承过程的函数,这个函数在内部以某种形式来增强对象
    function createObj(o) {
      var clone = Object.create(o);
      clone.sayName = function() {
        console.log('say HelloWorld');
      }
      return clone;
    }

寄生组合式继承

基础版本

    function Parent(name) {
      this.name = name;
      this.colors = ['red', 'green', 'blue'];
    }

    Parent.prototype.getName = function() {
      console.log(this, name);
    }

    function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
    }

    // test1:
    // 1. 设置子类实例的时候会调用父类的构造函数
    Child.prototype = new Parent();
    // 2. 创建子类实例的时候也会调用父类的构造函数
    var child1 = new Child('zhangsan', 18);   // Parent.call(this, name);


    // 思考:如何减少父类构造函数的调用次数呢?
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();

    // 思考:下面的这一句话可以吗?
    /* 分析:因为此时Child.prototype和Parent.prototype此时指向的是同一个对象,
            因此部分数据相当于此时是共享的(引用)。
            比如此时增加 Child.prototype.testProp = 1;
            同时会影响 Parent.prototype 的属性的。
          如果不模拟,直接上 es5 的话应该是下面这样吧
          Child.prototype = Object.create(Parent.prototype);*/
    Child.prototype = Parent.prototype;

    // 上面的三句话可以简化为下面的一句话
    Child.prototype = Object.create(Parent.prototype);



    // test2:
    var child2 = new Child('lisi', 24);

优化版本

    // 自封装一个继承的方法
    function object(o) {
      // 下面的三句话实际上就是类似于:var o = Object.create(o.prototype)
      function F(){};
      F.prototype = o.prototype;
      return new F();
    }

    function prototype(child, parent) {
      var prototype = object(parent.prototype);
      // 维护原型对象prototype里面的constructor属性
      prototype.constructor = child;
      child.prototype = prototype;
    }

    // 调用的时候
    prototype(Child, Parent)

创建对象的方法

  • 字面量创建
  • 构造函数创建
  • Object.create()
var o1 = {name: 'value'};
var o2 = new Object({name: 'value'});

var M = function() {this.name = 'o3'};
var o3 = new M();

var P = {name: 'o4'};
var o4 = Object.create(P)

原型

  • JavaScript 的所有对象中都包含了一个 __proto__ 内部属性,这个属性所对应的就是该对象的原型
  • JavaScript 的函数对象,除了原型 __proto__ 之外,还预置了 prototype 属性
  • 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 __proto__

【编程题与分析题】Javascript 之继承的多种实现方式和优缺点总结-LMLPHP

原型链

任何一个实例对象通过原型链可以找到它对应的原型对象,原型对象上面!

的实例和方法都是实例所共享的。

一个对象在查找以一个方法或属性时,他会先在自己的对象上去找,找不到时,他会沿着原型链依次向上查找。

注意: 函数才有prototype,实例对象只有有__proto__, 而函数有的__proto__是因为函数是Function的实例对象

instanceof原理

判断实例对象的__proto__属性与构造函数的prototype是不是用一个引用。如果不是,他会沿着对象的__proto__向上查找的,直到顶端Object。

判断对象是哪个类的直接实例

使用对象.construcor直接可判断

构造函数,new时发生了什么?

   var obj  = {};
   obj.__proto__ = Base.prototype;
   Base.call(obj);  
  1. 创建一个新的对象 obj;
  2. 将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
  3. Base函数对象的this指针替换成obj, 相当于执行了Base.call(obj);
  4. 如果构造函数显示的返回一个对象,那么则这个实例为这个返回的对象。 否则返回这个新创建的对象

类的声明

// 普通写法
function Animal() {
  this.name = 'name'
}

// ES6
class Animal2 {
  constructor () {
    this.name = 'name';
  }
}

继承

借用构造函数法

在构造函数中 使用Parent.call(this)的方法继承父类属性。

原理: 将子类的this使用父类的构造函数跑一遍

缺点: Parent原型链上的属性和方法并不会被子类继承

function Parent() {
  this.name = 'parent'
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

原型链实现继承

原理:把子类的prototype(原型对象)直接设置为父类的实例

缺点:因为子类只进行一次原型更改,所以子类的所有实例保存的是同一个父类的值。
当子类对象上进行值修改时,如果是修改的原始类型的值,那么会在实例上新建这样一个值;
但如果是引用类型的话,他就会去修改子类上唯一一个父类实例里面的这个引用类型,这会影响所有子类实例

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  this.type = 'child'
}

Child.prototype = new Parent();
var c1 = new Child();
var c2 = new Child();
c1.__proto__ === c2.__proto__

组合继承方式

组合构造函数中使用call继承和原型链继承。

原理: 子类构造函数中使用Parent.call(this);的方式可以继承写在父类构造函数中this上绑定的各属性和方法;
使用Child.prototype = new Parent()的方式可以继承挂在在父类原型上的各属性和方法

缺点: 父类构造函数在子类构造函数中执行了一次,在子类绑定原型时又执行了一次

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

Child.prototype = new Parent();

组合继承方式 优化1:

因为这时父类构造函数的方法已经被执行过了,只需要关心原型链上的属性和方法了

Child.prototype = Parent.prototype;

缺点:

  • 因为原型上有一个属性为constructor,此时直接使用父类的prototype的话那么会导致 实例的constructor为Parent,即不能区分这个实例对象是Child的实例还是父类的实例对象。
  • 子类不可直接在prototype上添加属性和方法,因为会影响父类的原型

注意:这个时候instanseof是可以判断出实例为Child的实例的,因为instanceof的原理是沿着对象的__proto__判断是否有一个原型是等于该构造函数的原型的。这里把Child的原型直接设置为了父类的原型,那么: 实例.__proto__ === Child.prototype === Child.prototype

组合继承方式 优化2 - 添加中间对象【最通用版本】:

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

Child.prototype = Object.create(Parent.prototype); //提供__proto__
Child.prototype.constrctor = Child;

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

创建JS对象的多种方式总结

工厂模式


    /**
    * 工厂模式创建对象
    * @param name
    * @return {Object}
    */
    function createPerson(name){
        var o = new Object();
        o.name = name;
        o.getName = function() {
          console.log(this.name);
        }
        return o;
    }
    var person = createPerson('zhangsan');
    console.log(person.__proto__ === Object.prototype); // true

构造函数模式

构造函数创建对象基础版本

    /**
    * 使用构造函数的方式来创建对象
    * @param name
    * @constructor
    */
    function Person(name) {
      this.name = name;
      this.getName = function() {
        console.log(this.name)
      }
    }
    var person = new Person('lisi');
    console.log(person.__proto__ === Person.prototype)

构造函数模式优化

    function Person(name) {
      this.name = name;
      this.getName = getName;
    }

    function getName() {
      console.log(this.name);
    }

    var person = new Person('zhangsan');
    console.log(person.__proto__ === Person.prototype);

原型模式

原型模式基础版

    function Person(name) {

    }
    Person.prototype.name = 'lisi';
    Person.prototype.getName = function() {
      console.log(this.name);
    }
    var person = new Person();
    console.log(Person.prototype.constructor)       // Person

原型模式优化版本一

    function Person(name) {

    }
    Person.prototype = {
        name: 'lisi',
        getName: function() {
          console.log(this.name);
        }
    }
    var person = new Person();
    console.log(Person.prototype.constructor)       // Object
    console.log(person.constructor == person.__proto__.constructor) // true

原型模式优化版本二

    function Person(name) {

    }
    Person.prototype = {
        constructor: Person,
        name: 'lisi',
        getName: function() {
          console.log(this.name)
        }
    }
    var person = new Person();

组合模式

    function Person(name) {
      this.name = name;
    }
    Person.prototype = {
        constructor: Person,
        getName: function() {
          console.log(this.name)
        }
    }
    var person = new Person('zhangsan');

动态原型模式

    // 第一种创建思路:
    function Person(name) {
       this.name = name;
       if (typeof this.getName !== 'function') {
           Person.prototype.getName = function() {
             console.log(this.name);
           }
       }
    }
    var person = new Person();

    // 第二种创建的思路:使用对象字面量重写原型上的方法
    function Person(name) {
      this.name = name;
      if (typeof this.getName !== 'function') {
          Person.prototype = {
              constructor: Person,
              getName: function() {
                console.log(this.name)
              }
          }
          return new Person(name);
      }
    }

    var person1 = new Person('zhangsan');
    var person2 = new Person('lisi');
    console.log(person1.getName());
    console.log(person2.getName());
    

寄生构造函数模式

    /**
    * 寄生构造函数模式
    * @param name
    * @return {Object}
    * @constructor
    */
   function Person(name){
        var o = new Object();
        o.name = name;
        o.getName = function() {
          console.log(this.name)
        }
        return o;
   }
   var person = new Person('zhangsan');
   console.log(person instanceof Person);   // false
   console.log(person instanceof Object);   // true


   // 使用寄生-构造函数-模式来创建一个自定义的数组
   /**
    * 特殊数组的构造器
    * @constructor
    */
   function SpecialArray() {
     var values = new Array();
     /*for (var i = 0, len = arguments.length; i < len; i++) {
         values.push(arguments[i]);
     }*/
     // 开始添加数据(可以直接使用apply的方式来优化代码)
     values.push.apply(values, arguments);

     // 新增的方法
     values.toPipedString = function(){
         return this.join('|');
     }

     return values;
   }

   // 使用new来创建对象
   var colors1 = new SpecialArray('red1', 'green1', 'blue1');
   // 不使用new来创建对象
   var colors2 = SpecialArray('red2', 'green2', 'blue2');

   console.log(colors1, colors1.toPipedString());
   console.log(colors2, colors2.toPipedString());

稳妥构造函数模式

    /**
    * 稳妥的创建对象的方式
    * @param name
    * @return {number}
    * @constructor
    */
    function Person(name){
        var o = new Object();
        o.sayName = function() {
           // 这里有点类似于在一个函数里面使用外部的变量
           // 这里直接输出的是name
          console.log(name);
        }
        return o;
    }
    var person =  Person('lisi');
    person.sayName();
    person.name = 'zhangsan';
    person.sayName();
    console.log(person instanceof Person);      // false
    console.log(person instanceof Object);      // false
11-22 09:10