继承

153 阅读2分钟

继承是为了复用对象。下面是几种继承方法:

  1. 原型链继承

    function A(name) {
        this.name = name;
        this.city = ['Guangzhou', 'Shenzhen'];
    };
    A.prototype.getName = function () {
        return this.name
    };
    function B(sex) {
        this.sex = sex;
    }
    B.prototype = new A('Bobo');
    var c = new B('male');
    c.getName(); // Bobo
    // 缺陷
    c.city.push('Shanghai');
    var d = new B('jojo');
    d.city; // [ 'Guangzhou', 'Shenzhen', 'Shanghai' ]
    

    优势:可以函数共享。

    缺陷:当上一个实例修改了原型中的 引用类型 的值,会影响到下一个实例。

  2. 构造函数继承

    function A (name) {
        this.city = ['Guangzhou', 'Shenzhen'];
        this.name = name;
    }
    A.prototype.getName = function() {
        return this.name;
    };
    function B(name, sex) {
        A.call(this, name);
        this.sex = sex;
    }
    var c = new B('coco', 'male');
    // c.getName(); // 无法共享方法
    c.name; // coco
    c.sex; // male
    c.city.push('Shanghai');
    var d = new B('jojo', 'female');
    d.city; // [ 'Guangzhou', 'Shenzhen' ]
    

    为什么叫构造函数继承呢,是因为对于新new对象(比如例子的c)来说,B就是他的构造函数,在B内进行继承,所以叫构造函数继承。

    构造函数继承,就是在子类函数里面使用 call, apply 来改变 this 指向,让它指向父类,改变上下文环境。由于父类方法是挂载在 prototype 所以子类无法获取到挂载的方法。

    优势:属性是实例借助构造函数自己生成的,所以各个实例的属性是各自独立的。

    缺陷:无法共享方法。

  3. 结合继承

    function A (name) {
      this.city = ['Guangzhou', 'Shenzhen'];
      this.name = name;
    }
    A.prototype.getName = function() {
      return this.name;
    };
    function B(name, sex) {
      A.call(this, name);
      this.sex = sex;
    }
    B.prototype = new A(); // A原型上的属性其实是没有必要的
    var c = new B('dodo', 'male');
    c.getName(); // dodo
    c.city.push('Shanghai');
    var d = new B('eoeo', 'female');
    d.city; // [ 'Guangzhou', 'Shenzhen' ]
    

    显而易见地,结合继承结合了原型继承和构造函数继承的优点。

    缺陷:子类原型上有一份多余的父类实例属性,父类构造函数被调用了2次,生成了2份,子类实例上的那一份屏蔽了子类原型上的。

  4. 寄生结合继承(圣杯模式)

    // 写法1
    function inherit(c, p) {
      function f() {}
      f.prototype = p.prototype;
      c.prototype = new f();
      c.prototype.constructor = c;
      // uber是超类,储存这个目标是继承于谁,可写可不写
      c.prototype.uber = p.prototype;
    }
    
    // 写法2
    var inherit2 = (function(c, p){
      var F = function(){};
      return function(c, p) {
        F.prototype = p.prototype;
        c.prototype = new F();
        c.uber = p.prototype;
        c.prototype.constructor = c;
      }
    })();
    
    function A (name) {
      this.city = ['Guangzhou', 'Shenzhen'];
      this.name = name;
    }
    A.prototype.getName = function() {
      return this.name;
    };
    function B(name, sex) {
      this.sex = sex;
      this.name = name;
    }
    
    inheirt(B, A);
    var c = new B('fofo', 'male');
    c.name; // fofo
    c.sex; // male
    // c.city; // 没有这个属性
    c.getName(); // fofo
    

    创建一个中间对象f,将父类的 prototype 指向中间对象f的 prototype ;然后子类实现f的原型继承,再矫正子类的constructor ,这样不会造成原型链的混乱。从而隔开了子类修改原型对父类造成的影响。但是无法继承父类的属性(比如父类的city属性);

    关键点在于 __proto__===constructor.prototype 的理解。对象会循着 __proto__ 向上寻找属性,如果自身没有的话。这样当 A.prototype = new B() 的时候,var c = new A()c 执行一个方法或者查找一个属性没有找到的时候,c.__proto__ -> A.prototype(new B()) -> A.prototype.__proto__ -> B.prototype -> ... -> null ; 所以这样是可以实现原型链查找,这也是继承的体现之处。