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
上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf()
寄生组合式继承是引用类型最理想的继承范式。YUI的YAHOO.lang.extend()
方法采用了寄生组合继承.
共同学习,写下你的评论
评论加载中...
作者其他优质文章