关于继承 ---- javascript 高级程序设计 第四版

194 阅读5分钟

继承

**重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象、原型有一个属性指向构造函数,而实例有一个内部指针指向原型

1.默认继承

默认情况下,所有的引用类型都继承自Object, 这也是通过原型链实现的。任何函数的默认原型都是一个Object的实例,这意味着这个实例有一个内部指针指向Object.prototype.这也是为什么自定义类型能够继承包括toString()、valueOf()在内的所有默认方法的原因   

原型与继承的关系

原型与实例的关系可以通过两种方式来确定。
1instanceof 操作符
    如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true
     instance instanceof Object // true
2isPrototypeOf() 方法。
    原型链中的每个原型都可以调用这个方法
    Object.prototype.instanceof(instance) // true

子类覆盖父类的方法则: 在原型赋值之后,重写父类原有的方法

以对象字面量方式创建原型方法会破坏之前的原型链

使用原型链继承存在的问题: 1、原型中包含的引用值会在所有实例间共享。2、子类型在实例化时不能给父类型的构造函数传参

2、盗用构造函数

基本思路: 为了解决原型包含引用值导致的继承问题,而出现的盗用构造函数。 在子类构造函数中调用父类构造函数。可以使用apply()和call()方法以新创建的对象上下文执行构造函数
相比于使用原型链,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参
为了确保父类构造函数不会覆盖子类定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性

盗用构造函数的缺点: 必须在构造函数中定义方法,因此函数不能重用。此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。因此,盗用构造函数基本上也不能单独使用

3、组合继承

综合了原型链和盗用构造函数。 基本思想:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。组合式继承是js中使用最多的继承模式,其保留了instanceof操作符和isPrototypeof()方法识别合成对象的能力

4、原型式继承

出发点: 即使不定义类型也可以通过原型实现对象之间的信息共享。
es5 新增object.create() 方法将原型式继承的概念规范化了。此方法接收两个参数: 1、作为新对象原型的对象, 2、给新对象定义额外属性的对象(可选)。在只有一个参数时,object.create()与下方的的object()方法效果相同。
原型式继承非常适合不需要单独创建构造函数,但仍需要在对象间共享信息的场合。属性中包含的引用值始终会在相关对象间共享,跟使用原型模式一样。
// 原型式继承的核心思想
    function object(o) {
        function F () {}
        F.prototype = o
        return new F ()
    }

5、寄生式继承

// 核心思想:
function createAnother (original) {
    let clone = object(original) // 通过调用函数创建一个新对象
    clone.sayHi = function() { // 以某种方式增强这个对象
     console.log('hi')
     return clone // 返回这个对象
}
寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式继承所必须的,任何返回新对象的函数都可以在这里使用

通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似

6、寄生式组合继承

组合继承其实也存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次:一次是在创建子类原型时调用,另一次是在子类构造函数中调用

function SuperType (name) {
    this.name = name
    this.colors = ['red']
}
SuperType.prototype.syaName = function () {
    console.log(this.name)
}
function SubType (name, age) {
    SuperType.call(this, name) // 第二次调用SuperType ()
    this.age = age
}
SubType.prototype = new SuperType() // 第一次调用SuperType ()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
    console.log(this.age)
}
var instance = new SubType('Nicholas', 29)

以上代码实现会出现两组name和colors属性:一组在实例上,另一组在SubType的原型上。这是调用两次superType构造函数的结果。以上问题可以使用寄生式组合继承解决

寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
// 寄生式组合继承的思想:
function inheritPrototype (SubType, superType) {
    let prototype = object(superType.prototype) // 创建对象
    prototype.constructor = subType // 增强对象
    subType.prototype = prototype // 赋值对象
}
function SuperType (name) {
    this.name = name
    this.colors = ['red']
}
SuperType.prototype.syaName = function () {
    console.log(this.name)
}
function SubType (name, age) {
    SuperType.call(this, name) // 第二次调用SuperType ()
    this.age = age
}
inheritPrototype(SubTypeSuperTypeSubType.prototype.sayAge = function () {
    console.log(this.age)
}

上述代码只调用了一次父类构造函数, 避免了SubType.prototype 上不必要也用不到的属性。寄生式组合继承可以算是引用类型继承的最佳模式