如何在JavaScript中实现简单的继承?
下面的例子将创建一个雇员类Employee,它从Person继承了原型prototype中的所有属性。
function Employee(name, sex, employeeID) {
this.name = name;
this.sex = sex;
this.employeeID = employeeID;
}
// 将Employee的原型指向Person的一个实例
// 因为Person的实例可以调用Person原型中的方法, 所以Employee的实例也可以调用Person原型中的所有属性。
Employee.prototype = new Person();
Employee.prototype.getEmployeeID = function() {
return this.employeeID;
};
var zhang = new Employee("ZhangSan", "man", "1234");
console.log(zhang.getName()); // "ZhangSan
上面关于继承的实现很粗糙,并且存在很多问题:
- 在创建Employee构造函数和原型(以后简称类)时,就对Person进行了实例化,这是不合适的。
- Employee的构造函数没法调用父类Person的构造函数,导致在Employee构造函数中对name和sex属性的重复赋值。
- Employee中的函数会覆盖Person中的同名函数,没有重载的机制(和上一条是一个类型的问题)。
- 创建JavaScript类的语法过于零散,不如C#/Java中的语法优雅。
- 实现中有constructor属性的指向错误。
正因为JavaScript本身没有完整的类和继承的实现,并且我们也看到通过手工实现的方式存在很多问题, 因此对于这个富有挑战性的任务网上已经有很多实现了:
- Douglas Crockford - Prototypal Inheritance in JavaScript
- Douglas Crockford - Classical Inheritance in JavaScript
- John Resig - Simple JavaScript Inheritance
- Dean Edwards - A Base Class for JavaScript Inheritance
- Prototype
- Mootools
- Extjs
这篇文章将会给出一个可行的实例代码具体分析这些实现,最终达到对JavaScript中如何实现类和继承,希望对你理解JavaScript有一定的帮助。
( function() {
jClass = function() {
};
// 当前是否处于创建类的阶段
var initializing = false;
jClass.extend = function( prop ) {
var baseClass = null;
// 如果调用当前函数的对象(这里是函数)不是Class,则是父类
if ( this !== jClass ) {
baseClass = this;
}
// 本次调用所创建的类(构造函数)
var jConstructor = function() {
// 如果当前处于实例化类的阶段,则调用init原型函数
if ( !initializing ) {
// 如果父类存在,则实例对象的baseprototype指向父类的原型
// 这就提供了在实例对象中调用父类方法的途径
if ( baseClass ) {
this._superprototype = baseClass.prototype;
}
this.init.apply( this, arguments );
}
}
// 如果此类需要从其它类扩展
if ( baseClass ) {
initializing = true;
jConstructor.prototype = new baseClass();
jConstructor.prototype.constructor = jConstructor;
initializing = false;
}
// 新创建的类自动附加extend函数
jConstructor.extend = arguments.callee;
// 覆盖父类的同名函数
for ( var name in prop ) {
if ( prop.hasOwnProperty( name ) ) {
// 如果此类继承自父类baseClass,并且父类原型中存在同名函数name
if ( baseClass &&
typeof ( prop[name] ) === "function" &&
typeof ( jConstructor.prototype[name] ) === "function" &&
/\b_super\b/.test( prop[name] ) ) {
// 重定义函数name -
// 首先在函数上下文设置this._super指向父类原型中的同名函数
// 然后调用函数prop[name],返回函数结果
// 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,
// 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。
// 这是JavaScript框架开发中常用的技巧。
jConstructor.prototype[name] = ( function( name, fn ) {
return function() {
this._super = baseClass.prototype[name];
return fn.apply( this, arguments );
};
} )( name, prop[name] );
} else {
jConstructor.prototype[name] = prop[name];
}
}
}
return jConstructor;
};
} )();
// Demo
var Person = jClass.extend( {
init: function( name ) {
this.name = name;
},
getName: function( prefix ) {
return prefix + this.name;
}
} );
var Employee = Person.extend( {
init: function( name, employeeID ) {
// 调用父类的方法
this._super( name );
this.employeeID = employeeID;
},
getEmployeeIDName: function() {
// 注意:我们还可以通过这种方式调用父类中的其他函数
var name = this._superprototype.getName.call( this, "Employee name: " );
return name + ", Employee ID: " + this.employeeID;
},
getName: function() {
// 调用父类的方法
return this._super( "Employee name: " );
}
} );
var zhang = new Employee( "ZhangSan", "1234" );
console.log( zhang.getName( ) ); // "Employee name: ZhangSan"
console.log( zhang.getEmployeeIDName() ); // "Employee name: ZhangSan, Employee ID: 1234"