首页 > 代码库 > JavaScript继承的实现

JavaScript继承的实现

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