手记

javascript中继承的几种实现

原型链

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。所谓的基于原型的继承就是子类的原型等于父类的实例,如下代码所示:

// 父类
function SuperType () {
    this.superProperty = '父类属性'
}
SuperType.prototype.getSuperValue = function () {
    return this.superProperty
}

// 子类
function SubType () {
    this.subProperty = '子类属性'
}
// 基于原型的继承(子类的原型属性等于父类的实例)[先重写原型,再在原型上添加方法]
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
    return this.subProperty
}

var instance = new SubType()
console.info(instance.getSubValue()) // 子类属性
console.info(instance.getSuperValue()) // 父类属性

请注意以下的一点:

function SuperType () {
    this.numbers = [1,2,3]
}

function SubType () {

}

SubType.prototype = new SuperType()

var instance1 = new SubType()
instance1.numbers.push(4)
console.info(instance1.numbers) // [1,2,3,4]

var instance2 = new SubType()
console.info(instance2.numbers) // [1,2,3,4]

在上面的例子中,我们的instance2的numbers值并不是我们期待的[1,2,3],原因就在于在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

借用构造函数

即在子类型构造函数的内部调用超类型构造函数,有点类似于java中的super调用父类的构造器,在js中我们可以使用call或者apply.

function SuperType() {
    this.numbers = [1, 2, 3]
}

function SubType() {
    // 继承SuperType
    SuperType.call(this)
}

var instance1 = new SubType()
instance1.numbers.push(4)
console.info(instance1.numbers) // [1,2,3,4]

var instance2 = new SubType() 
console.info(instance2.numbers) // [1,2,3]

这种方式显然没有原型模式的缺点,结果,SubType 的每个实例就都会具有自己的numbers属性的副本了。

相对于原型继承,调用父类构造函数的方式还可以向父类的构造函数中传递参数.

function SuperType(name) {
    this.name = name
}

function SubType() {
    // 继承SuperType,同时传递参数
    SuperType.call(this, 'Jelly')
    // 实例属性
    this.age = 29
}

var instance = new SubType()
console.info(instance.name, instance.age) // Jelly 29
组合继承

该方式是原型继承和借用构造函数的组合.用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function SuperType(name) {
    this.name = name
    this.numbers = [1,2,3]
}
SuperType.prototype.sayName = function () {
    console.info(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 () {
    console.info(this.age)
}

var instance1 = new SubType('A',23)
instance1.numbers.push(-1)
console.info(instance1.numbers) // [ 1, 2, 3, -1 ]
instance1.sayName() // A
instance1.sayAge()  // 23

var instance2 = new SubType('B',27)
console.info(instance2.numbers) // [ 1, 2, 3 ]
instance2.sayName() // B
instance2.sayAge()  // 27

这种方式结合了原型链和借用构造函数的优点,是js中常见的继承方式,但是缺点也是显而易见的,我们2次调用了父类的构造函数,导致了一个对象的实例上有name和numbers属性(第二次调用),原型上也有name和numbers属性(第一次调用),解决这个问题我们引出了最后一种继承的方式:

寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。基本模式如下:

/**
 * @param  subType   子类型构造函数
 * @param  superType 父类型构造函数
 */
function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype) // 创建超类型的原型的副本
    prototype.constructor = subType             // 为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性
    subType.prototype = prototype               // 将新创建的对象(即副本)赋值给子类型的原型
}

一个继承的范例如下:

/**
 * @param  subType   子类型构造函数
 * @param  superType 父类型构造函数
 */
function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype) // 创建对象
    prototype.constructor = subType // 增强对象
    subType.prototype = prototype // 指定对象
}

function SuperType(name) {
    this.name = name
    this.numbers = [1,2,3]
}
SuperType.prototype.sayName = function () {
    console.info(this.name)
}

function SubType (name,age) {
    // 继承属性
    SuperType.call(this,name) 
    // 实例属性
    this.age = age
}
inheritPrototype(subType,superType)
SubType.prototype.sayAge = function () {
    console.info(this.age)
}

这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf() 寄生组合式继承是引用类型最理想的继承范式。YUIYAHOO.lang.extend()方法采用了寄生组合继承.

8人推荐
随时随地看视频
慕课网APP

热门评论

对于理解原型很有帮助,32个赞

查看全部评论