ES5中的继承

1.原型链

通过原型链实现继承的本质是重写子类的原型对象,代之以父类的实例。
代码如下:

//1 原型链
function Father() {
    this.name = 'father'
}
Father.prototype.skills = function () {
    console.log("QWER")
}
function Son() {
    this.weapons = "sword"
}
Son.prototype = new Father()
Son.prototype.run = function () {
    console.log('i am running')
}

var mike = new Son()
console.log(mike.name) //father
mike.skills() //QWER

在上面的代码中,我们可以看到用父类Father的实例替换了子类Son的原型对象,这个新原型对象包含了父类的所有属性和方法,然后我们又在这个新的原型上定义了Son自己的方法。但是这个方法问题也是非常明显,我们知道原型上的方法或者属性都是所有实例共享,假设父类中包含引用类型的属性,当我们通过这种方法来继承时,修改其中一个实例的引用类型属性,则会影响到其他实例。
我们仅在上述代码中父类加一个hobby数组来看下,代码如下:

function Father() {
    this.name = 'father'
    this.hobby = ['sing','jump','rap']
}
Father.prototype.skills = function () {
    console.log("QWER")
}
function Son() {
    this.weapons = "sword"
}
Son.prototype = new Father()
Son.prototype.run = function () {
    console.log('i am running')
}

var mike1 = new Son()
var mike2 = new Son()
console.log(mike1.hobby) //["sing", "jump", "rap"]
console.log(mike2.hobby)//["sing", "jump", "rap"]
console.log(mike1.hobby === mike2.hobby) //true
mike1.hobby.push('basketball')
console.log(mike1.hobby) //["sing", "jump", "rap","basketball"]
console.log(mike2.hobby)//["sing", "jump", "rap","basketball"]

上面代码中我们可以看到仅仅修改了mike1中的hobby,但mike2也受到了影响。
而原型链的另一个问题是创建实例时我们没办法向父类构造函数传递参数。

2.借用构造函数

借用构造函数的思想是我们在子类构造函数中调用父类构造函数,然后通过call或者apply来修改this指向。
代码如下:

    function Father(name) {
        this.name = name
        this.hobby = ['sing', 'jump', 'rap']
    }
    Father.prototype.skills = function () {
        console.log("QWER")
    }
    function Son(name) {
        Father.call(this, name)  //这里修改了this指向并传递了参数
        this.height = "150"
    }
    Son.prototype.run = function () {
        console.log('i am running')
    }
    var mike1 = new Son('xiaoming')
    var mike2 = new Son('xiaohong')
    mike1.hobby.push("basketball")
    console.log(mike1.hobby) //["sing", "jump", "rap", "basketball"] 
    console.log(mike2.hobby) //["sing", "jump", "rap"] 
   // mike1.skills() 报错

上面的代码中我们在子类构造函数里调用call修改this指向,解决了原型链中引用类型共用的问题。但在借用构造函数中,我们定义在父类原型中的方法对子类是不可见的,所以上面mike1.skills会报错,若想定义公用方法,只能在父类构造函数中定义而不是原型,但是这样一来,也就违背了函数复用的理念。

3.组合继承

组合继承结合了原型链和借用构造函数的优点,其思想是通过原型链来继承原型上的属性和方法,通过借用构造函数继承实例上的属性。
代码如下:

    function Father(name) {
        this.name = name
        this.hobby = ['sing', 'jump', 'rap']
    }
    Father.prototype.skills = function () {
        console.log("QWER")
    }
    function Son(name) {
        Father.call(this, name)  //第二次调用父构造函数
        this.height = "150"
    }
    Son.prototype = new Father() //第一次调用父构造函数
    Son.prototype.run  =  function () {
    console.log("i am running") 
    }
    var mike1 = new Son('xiaoming')
    var mike2 = new Son('xiaohong')
    mike1.hobby.push("basketball")
    console.log(mike1.hobby) //["sing", "jump", "rap", "basketball"] 
    console.log(mike2.hobby) //["sing", "jump", "rap"] 
    mike1.skills()  //QWER

组合继承弥补了原型链和借用构造函数的缺点,将两者的优点融合在一起,但是它也有自己的不足,因为任何情况下它都会调用两次父构造函数,第一次是在改写子类原型时,第二次是在子类实例化时调用。而且第一次调用时我们会继承父类上的属性,此时继承的属性在原型上,第二次调用时又创建了一遍相同的属性,此时属性在构造函数上,当我们访问继承的属性时,访问的是构造函数上的属性。由此可见组合继承会在构造函数和原型上创建多余重复的属性。

4.寄生组合式继承

在组合继承中我们通过修改子类原型来继承方法时,会调用父类构造函数,但是站在开发者的角度来说,只不过是想要拿到父类原型的副本而已,然后将这个副本给到子类原型上,这样我们就不用为了指定子类型的原型来调用父构造函数。
代码如下:

    function inheritPrototype(son, father) {
        var prototype = father.prototype //创建父类原型副本
        prototype.constructor = son //为副本添加constructor属性,因为重写原型后会丢失默认的constructor
        son.prototype = prototype //将创建的副本赋于子类原型
    }
    function Father(name) {
        this.name = name
        this.hobby = ['sing', 'jump', 'rap']
    }
    Father.prototype.skills = function () {
        console.log("QWER")
    }
    function Son(name) {
        Father.call(this, name)  
        this.height = "150"
    }
    inheritPrototype(Son,Father);
    Son.prototype.run = function(){
        console.log("i am running")
    }

上面代码中定义了一个inheritPrototype方法,这个方法接受两个参数子构造函数和父构造函数,方法中每步操作见注释。然后在修改子类原型对象时并没像组合继承里面那样调用父构造函数,而是调用了inheritPrototype方法,再给子类原型上定义自己的方法。
这个例子中高效体现在只调用了一次父构造函数,而且不会像组合继承那样创建不必要的重复的属性。因此寄生组合式继承是比较理想的继承模式。

本文参考《JavaScript高级程序设计(第3版)》第6章:面向对象的程序设计,若有理解不足之处,欢迎指出。

转载请注明出处,谢谢。

你可能感兴趣的:(ES5中的继承)