如何在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"