本文深入解析JavaScript中的寄生组合式继承模式。通过对比原型链继承与借用构造函数继承的局限性,阐述组合继承调用两次父类的问题,并介绍如何利用Object.create()实现原型式继承以修复该缺陷。文章提供完整代码示例,展示如何结合两者实现高效、完美的JS类继承机制。
JavaScript寄生组合式继承概述
本文详细介绍JavaScript中的寄生组合式继承模式,旨在帮助读者理解其核心原理并提供参考示例。
《JavaScript高级程序设计》一书中已包含完整的代码实现,理解这些代码即可掌握该继承模式的运作机制。
在JavaScript中,为对象定义属性主要存在两种不同的方式。
//通过执行构造函数设置属性
function A(){
this.a = 1;
}
//通过原型设置属性
A.prototype.b = 1;
因此,我们需要明确这两种方式的区别。
若子类Sub需要继承父类Super,不仅需要继承父类prototype中的属性,还必须执行父类的构造函数。
具体而言,子类Sub继承父类Super时,既要通过原型链继承原型属性和方法,又要在子类构造函数内部调用父类构造函数以继承实例属性。
原型属性继承机制
1. 继承prototype下的属性
前文指出,Super类的prototype属性并未被直接继承,因此需要额外处理这部分继承。
直接使用「=」赋值不可行,因为若修改Sub.prototype中的属性,不应影响Super.prototype中的对象,即不能执行Sub.prototype=Super.prototype。
首先定义一个创建对象副本的方法。
function object(o){
function A(){}
A.prototype = o
var ox = new A()
return ox
}
上述函数生成的对象ox拥有对象o的全部属性(包括原型链上的),且修改ox的属性不会影响o,这相当于对o进行了浅拷贝。
这种原型式继承方法在许多类库源码中均有应用。
简而言之,原型式继承无需实例化父类,而是通过实例化一个临时副本来实现相同的原型链继承(即子类的原型指向父类副本的实例,从而实现原型共享)。
提示:众所周知,原型链继承是子类的原型指向父类的实例从而实现原型共享;而原型式继承则是子类的原型指向父类副本的实例从而实现原型共享。
ECMAScript 5通过新增Object.create()方法对原型式继承进行了规范化。
使用object方法,可以将Super.prototype的属性「复制」到Sub.prototype上,同时还需要修正constructor的指向。
function inherit(subType,superType){
var prototype=Object.create(superType.prototype);
prototype.constructor=subType;
subType.prototype=prototype;
}
实例属性继承与构造函数调用
2. 分别执行父类和子类的构造函数,继承这部分下的属性:
//父类
function Super(){
this.sss=1
}
//子类
function Sub(){
//arguments是Sub收到的参数,将这个参数传给Super
Super.apply(this, arguments)
}
//实例
sub = new Sub()
执行Super.apply(this, arguments)这一句代码,将Super类作为普通函数调用,但此时Super类的this被替换为Sub类的this,且Sub接收到的参数也传递给了Super。
最终执行结果相当于sub.sss=1。
各类继承方式对比与组合继承缺陷
附上各种继承方式的特点和优缺点分析。
过去一段时间,由于JavaScript关于类实现继承缺乏规范,导致出现了各种各样的继承代码;但实际上,无论代码如何变化,继承都基于两种基本方式:
1. 通过原型链,即子类的原型指向父类的实例从而实现原型共享。 2. 借用构造函数,即通过js的apply、call实现子类调用父类的属性、方法;
原型链方式可以实现所有属性和方法的共享,但无法做到属性和方法的独享(例如,若Sub1修改了父类的函数,其他所有子类Sub2、Sub3...想调用旧的函数就无法实现了)。
而借用构造函数除了能独享属性和方法外,还能在子类构造函数中传递参数,但代码无法复用。总体而言,这种方式可以实现所有属性和方法的独享,但无法做到属性和方法的共享(例如,Sub1新增了一个函数,若想让Sub2、Sub3...都能使用,则无法实现,只能让Sub2、Sub3...各自在构造函数中新增)。
组合继承将上述两种继承方式结合使用:共享的属性和方法通过原型链继承实现,独享的属性和方法通过借用构造函数实现。因此,组合继承几乎完美实现了JavaScript的继承;为何说是“几乎”?因为细心的开发者发现组合继承存在一个小bug:实现时调用了两次超类(父类),性能表现不佳。如何解决呢?于是“寄生继承”应运而生。
寄生继承(原型式继承)无需实例化父类,而是直接实例化一个临时副本来实现相同的原型链继承(即子类的原型指向父类副本的实例,从而实现原型共享)。
“寄生组合继承”利用“寄生继承”修复了“组合继承”的小bug,从而使JavaScript能够完美实现继承。
寄生组合式继承代码实现
实例代码:
function SuperType(name,colors){
this.name=name;
this.colors=colors;
}
SuperType.prototype.getSuperProperty=function(){ return this.name; }
function SubType(job,name,colors){
SuperType.call(this,name,colors);
this.job=job;
}
SubType.prototype.getSubPrototype=function(){ return this.job; }
function inherit(subType,superType){
var prototype=Object.create(superType.prototype);
prototype.constructor=subType;
subType.prototype=prototype;
}
inherit(SubType,SuperType);
var instance=new SubType("doctor","John",["red","green"]);
console.log(instance.getSubPrototype()); //输出"doctor"
console.log(instance.getSuperProperty()); //输出"John",成功调用在父类原型定义的方法
属性继承代码是SuperType.call(this,name,colors);
原型继承代码是inherit(SubType,SuperType);
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。