继承的话可以进行代码的复用。大白话来说:A有某属性或者方法,而B现在也需要这些属性和方法,这个时候使用继承,B继承于A。那么B就能获取的需要的属性或者方法。

接下来看看JS里面继承的几种实现方式:

原型链

这个应该不用多讲,就是利用原型链的特性。一个构造函数A的原型对象(A.prototype)是另外一个构造函数的实例(A.prototype = new B())。这样由构造函数A产生的实例首先继承于A.prototype,此外A.prototype.__proto__还指向了B.prototype。因此由构造函数A产生的实例a,还继承了B.prototype上的原型和方法。

这样就是一个基本的套路:利用原型链完成继承。但是这种继承方式有它局限的地方。就是如果B.prototype的某个属性是引用类型的话,在实例当中对它进行更改,那么原型上的属性也会发生变化。那么这种情况下,其他的实例上的属性也会受到影响。

借用构造函数

这个方法换了一个套路,主要能解决的问题就是避免了实例修改了原型上的引用类型的值而带来的负面作用。主要的思路就是:
首先定义一个超类的构造函数,然后在子类的构造函数中去调用这个超类,调用的过程中注意this的指向。

    function SuperType() {
        this.colors = ['red', 'blue', 'green'];
    }
    function SubType() {
        SuperType.call(this);   //将this的指向改变到当前作用域
    }
    var instance1 = new SubType(); //这样instance1就会获取到构造函数SuperType定义的属性或方法。

除了能继承超类的方法,还可以自定义属性或方法:

    function SuperType(name) {
        this.name = name;
    }
    
    function SubType(age) {
        SuperType.call(this);
        
        this.age = age;
    }
    
    var instance1 = new SubType('xx', 100);

这样有构造函数SubType产生的实例不仅能有自己定义的属性,还能从SuperType那里获得相应的属性或方法。

但是这种方式也有它的局限性:父类的方法和属性都是在子类的构造函数中进行调用的,子类的实例确实有了这些方法或者属性。但是在父类的原型上定义的方法或者属性是无法被子类的实例去获取的。因为并没有存在的相应的原型链。即子类的实例的__proto__和父类并没有什么关系。因此代码复用也无从谈起。

组合继承

组合继承主要是弥补了以上2种继承方式的不足:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承:

    function SuperType(name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function() {
        console.log(this.name);
    }
    
    function SubType(name, age) {
        SuperType.call(this, name); //调用父类的构造函数,生成自己的name属性
        
        this.age = age;
    }
    
    SubType.prototype = new SuperType(); //完成对父类原型上的方法和属性的继承。因为SubType的实例能获取到SubType原型对象上的方法。而SubType的原型对象的__proto__属性指向父类的原型对象来。这样就完成了对父类原型对象的方法和属性的继承。

原型式继承

先看下实现代码:

    function Object(o) {
        function F() {};
        F.prototype = o;
        return new F();
    }

Object函数内部定义一个构造函数,然后将这个构造函数的原型属性指向传入的对象,然后由这个构造函数产生实例。这样这个实例就能继承对象o上的属性和方法了。

寄生式继承

顾名思义:提供一个载体完成继承功能,最后到达的效果就是好像是这个载体自身完成了这个继承功能一样。

    function createAnother(o) {
        var clone = object(o);  //调用这个方法才是完成继承的功能。并生成实例
        
        clone.sayHi = function() {  //在实例上添加方法
            console.log('Hi');
        }
        return clone;
    }

这种方式也有不便的地方,就是依赖传入的对象,如果是个某个构造函数的实例,那么可以完成相应的方法和属性的继承。

寄生组合式继承

之前提到的组合继承,

    function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
}
    SuperType.prototype.sayName = function(){
        alert(this.name);
 };
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

这里存在的问题是

    SubType.prototype = new SuperType(); //定义了SubType.prototype原型对象。有SuperType实例上的属性和方法。
    
    //但是在子类调用父类的构造函数的时候覆盖了原型对象上的属性
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }

寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路就是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType;  //增强对象
    subType.prototype = prototype;  //指定对象
}

在函数的内部,首先创建一个继承于父类原型superType.prototype的实例,将其当做子类subType的原型对象。这样就完成了子类到父类的继承。


苹果小萝卜
5.1k 声望356 粉丝

Github: [链接]