本文系统梳理JavaScript继承机制的演进过程,从直接原型链、构造函数到组合模式,深入解析寄生组合式继承的优势。重点剖析simple-inheritance库源码,揭示其通过重写父类方法实现类似强类型语言继承的精妙设计,帮助开发者掌握高效且规范的JS面向对象编程技巧。
JavaScript继承机制概述
这是一个常被讨论但难以被系统理解的话题。JavaScript语言本身对继承的支持并不完善,需要开发者自行构建完整的继承机制。接下来,我们将由浅入深地系统掌握JavaScript继承的使用技巧。
原型链与构造函数的局限
1. 直接使用原型链
这是最直观但也最粗糙的方式,通常不适用于实际项目。一个简单的示例如下:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
//继承
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType();
这种方式的主要问题是原型中的属性会被所有实例共享。当通过一个实例修改继承过来的属性时,会影响其他实例。这显然不符合常规意义上的继承定义。
2. 使用构造函数
构造函数本质上只是一个函数,可以在任何作用域中调用。在子构造函数中调用父构造函数,即可实现简单的继承。
function SuperType(){
this.colors = {"red","blue","green"}
}
function SubType(){
SuperType.call(this);
}
var instance = new SubType();
这种实现避免了多个实例共享属性的问题,但引入了新问题,例如无法共享函数,且 instance instanceof SuperType 的结果为 false。
组合继承与寄生组合式继承
3. 组合使用原型和构造函数
function SuperType(name){
this.name = name;
this.colors = {"red","blue","green"}
}
SuperType.prototype.sayName = function(){
//code
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
var instance = new SubType();
组合使用原型和构造函数是JavaScript中最常用的继承模式。这种方式下,每个实例拥有自己的属性,同时可以共享原型中的方法。然而,其缺点在于无论何种情况,都会调用两次超类构造函数:一次在创建子类原型时,另一次在子类构造函数内部。如何解决这一问题呢?
4. 寄生组合式继承
SubType的原型不一定是SuperType的实例,只需是一个构造函数的原型为SuperType原型的普通对象即可。Douglas Crockford提出的方法如下:
function obejct(o){
function F(){};
F.prototype = o;
return new F();
}
这实际上就是ES5中Object.create的实现原理。我们可以据此修改本文中的第3种方案:
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name){
this.name = name;
this.colors = {"red","blue","green"}
}
SuperType.prototype.sayName = function(){
//code
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
var instance = new SubTYpe();
寄生组合式继承已经是一种非常优秀的继承实现机制,足以应对日常开发需求。如果我们提出更高要求:如何在子类中调用父类的方法呢?
simple-inheritance源码深度解析
5. simple-inheritance库的实现
初看这段代码令人费解,起初我也持怀疑态度,但深入后发现大牛的思想无处不在。我对每一行代码都添加了详细注释。如果你想了解细节,请务必深入研究并读懂每一行。我认为这个实现最精妙之处在于按需重写父类方法,在实例对象中可以通过 _super 调用父类的同名方法,类似于Java的实现方式。
(function(){
//initializing用于控制类的初始化,非常巧妙,请留意下文中使用技巧
//fnTest返回一个正则比表达式,用于检测函数中是否含有_super,这样就可以按需重写,提高效率。当然浏览器如果不支持的话就返回一个通用正则表达式
var initializing = false,fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
//所有类的基类Class,这里的this一般是window对象
this.Class = function(){};
//对基类添加extend方法,用于从基类继承
Class.extend = function(prop){
//保存当前类的原型
var _super = this.prototype;
//创建当前类的对象,用于赋值给子类的prototype,这里非常巧妙的使用父类实例作为子类的原型,而且避免了父类的初始化(通过闭包作用域的initializing控制)
initializing = true;
var prototype = new this();
initializing = false;
//将参数prop中赋值到prototype中,这里的prop中一般是包括init函数和其他函数的对象
for(var name in prop){
//对应重名函数,需要特殊处理,处理后可以在子函数中使用this._super()调用父类同名构造函数, 这里的fnTest很巧妙:只有子类中含有_super字样时才处理从写以提高效率
prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name])?
(function(name,fn){
return function(){
//_super在这里是我们的关键字,需要暂时存储一下
var tmp = this._super;
//这里就可以通过this._super调用父类的构造函数了
this._super = _super[name];
//调用子类函数
fn.apply(this,arguments);
//复原_super,如果tmp为空就不需要复原了
tmp && (this._super = tmp);
}
})(name,prop[name]) : prop[name];
}
//当new一个对象时,实际上是调用该类原型上的init方法,注意通过new调用时传递的参数必须和init函数的参数一一对应
function Class(){
if(!initializing && this.init){
this.init.apply(this,arguments);
}
}
//给子类设置原型
Class.prototype = prototype;
//给子类设置构造函数
Class.prototype.constructor = Class;
//设置子类的extend方法,使得子类也可以通过extend方法被继承
Class.extend = arguments.callee;
return Class;
}
})();
通过使用simple-inheritance库,我们可以以非常简单的方式实现继承,是否发现它特别像强类型语言的继承风格?
var Human = Class.extend({
init: function(age,name){
this.age = age;
this.name = name;
},
say: function(){
console.log("I am a human");
}
});
var Man = Human.extend({
init: function(age,name,height){
this._super(age,name);
this.height = height;
},
say: function(){
this._super();
console.log("I am a man");
}
});
var man = new Man(21,'bob','191');
man.say();
由浅入深讲解Javascript继承机制与simple-inheritance源码分析,希望本文分享能够帮助到大家。