注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法。
首先让我们来回顾一下第一章中介绍的例子:
function Person(name) {<BR> this.name = name;<BR> }<BR> Person.prototype = {<BR> getName: function() {<BR> return this.name;<BR> }<BR> }<br><br> function Employee(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> }<BR> Employee.prototype = new Person();<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<BR> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan" <BR>
从上一篇文章中关于constructor的描述,我们知道Employee实例的constructor会有一个指向错误,如下所示:
var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.constructor === Employee); // false<BR> console.log(zhang.constructor === Object); // true <BR>
function Employee(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> }<BR> Employee.prototype = new Person();<BR> Employee.prototype.constructor = Employee;<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<BR> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.constructor === Employee); // true<BR> console.log(zhang.constructor === Object); // false<BR>
但另一方面,我们又必须依赖于这种机制来实现继承。 解决办法是不在构造函数中初始化数据,而是提供一个原型方法(比如init)来初始化数据。
// 空的构造函数<BR> function Person() {<BR> }<BR> Person.prototype = {<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> }<BR> // 空的构造函数<BR> function Employee() {<BR> }<BR> // 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数<BR> Employee.prototype = new Person();<BR> Employee.prototype.constructor = Employee;<BR> Employee.prototype.init = function(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> };<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<BR>
var zhang = new Employee();<BR> zhang.init("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan"<BR>
必须达到两个效果,构造类时不要调用init函数和实例化对象时自动调用init函数。看来我们需要在调用空的构造函数时有一个状态标示。
// 创建一个全局的状态标示 - 当前是否处于类的构造阶段<BR> var initializing = false;<BR> function Person() {<BR> if (!initializing) {<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> Person.prototype = {<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> }<BR> function Employee() {<BR> if (!initializing) {<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 标示当前进入类的创建阶段,不会调用init函数<BR> initializing = true;<BR> Employee.prototype = new Person();<BR> Employee.prototype.constructor = Employee;<BR> initializing = false;<BR> Employee.prototype.init = function(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> };<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<br><br> // 初始化类实例时,自动调用类的原型函数init,并向init中传递参数<BR> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan"<BR>
我们需要引入一个全局的函数来简化类的创建过程,同时封装内部细节避免引入全局变量。
// 当前是否处于创建类的阶段<BR> var initializing = false;<BR> function jClass(baseClass, prop) {<BR> // 只接受一个参数的情况 - jClass(prop)<BR> if (typeof (baseClass) === "object") {<BR> prop = baseClass;<BR> baseClass = null;<BR> }<BR> // 本次调用所创建的类(构造函数)<BR> function F() {<BR> // 如果当前处于实例化类的阶段,则调用init原型函数<BR> if (!initializing) {<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 如果此类需要从其它类扩展<BR> if (baseClass) {<BR> initializing = true;<BR> F.prototype = new baseClass();<BR> F.prototype.constructor = F;<BR> initializing = false;<BR> }<BR> // 覆盖父类的同名函数<BR> for (var name in prop) {<BR> if (prop.hasOwnProperty(name)) {<BR> F.prototype[name] = prop[name];<BR> }<BR> }<BR> return F;<BR> };<BR>
var Person = jClass({<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> });<BR> var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> },<BR> getEmployeeID: function() {<BR> return this.employeeID;<BR> }<BR> });<br><br> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan"<BR>
我们可以通过为实例化对象提供一个base的属性,来指向父类(构造函数)的原型,如下:
// 当前是否处于创建类的阶段<BR> var initializing = false;<BR> function jClass(baseClass, prop) {<BR> // 只接受一个参数的情况 - jClass(prop)<BR> if (typeof (baseClass) === "object") {<BR> prop = baseClass;<BR> baseClass = null;<BR> }<BR> // 本次调用所创建的类(构造函数)<BR> function F() {<BR> // 如果当前处于实例化类的阶段,则调用init原型函数<BR> if (!initializing) {<BR> // 如果父类存在,则实例对象的base指向父类的原型<BR> // 这就提供了在实例对象中调用父类方法的途径<BR> if (baseClass) {<BR> this.base = baseClass.prototype;<BR> }<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 如果此类需要从其它类扩展<BR> if (baseClass) {<BR> initializing = true;<BR> F.prototype = new baseClass();<BR> F.prototype.constructor = F;<BR> initializing = false;<BR> }<BR> // 覆盖父类的同名函数<BR> for (var name in prop) {<BR> if (prop.hasOwnProperty(name)) {<BR> F.prototype[name] = prop[name];<BR> }<BR> }<BR> return F;<BR> };<BR>
var Person = jClass({<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> });<BR> var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> // 调用父类的原型函数init,注意使用apply函数修改init的this指向<BR> this.base.init.apply(this, [name]);<BR> this.employeeID = employeeID;<BR> },<BR> getEmployeeID: function() {<BR> return this.employeeID;<BR> },<BR> getName: function() {<BR> // 调用父类的原型函数getName<BR> return "Employee name: " + this.base.getName.apply(this);<BR> }<BR> });<br><br> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "Employee name: ZhangSan"<BR>
目前为止,我们已经修正了在第一章手工实现继承的种种弊端。 通过我们自定义的jClass函数来创建类和子类,通过原型方法init初始化数据, 通过实例属性base来调用父类的原型函数。
唯一的缺憾是调用父类的代码太长,并且不好理解, 如果能够按照如下的方式调用岂不是更妙:
var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> // 如果能够这样调用,就再好不过了<BR> this.base(name);<BR> this.employeeID = employeeID;<BR> }<BR> });<BR>
// 当前是否处于创建类的阶段<BR> var initializing = false;<BR> function jClass(baseClass, prop) {<BR> // 只接受一个参数的情况 - jClass(prop)<BR> if (typeof (baseClass) === "object") {<BR> prop = baseClass;<BR> baseClass = null;<BR> }<BR> // 本次调用所创建的类(构造函数)<BR> function F() {<BR> // 如果当前处于实例化类的阶段,则调用init原型函数<BR> if (!initializing) {<BR> // 如果父类存在,则实例对象的baseprototype指向父类的原型<BR> // 这就提供了在实例对象中调用父类方法的途径<BR> if (baseClass) {<BR> this.baseprototype = baseClass.prototype;<BR> }<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 如果此类需要从其它类扩展<BR> if (baseClass) {<BR> initializing = true;<BR> F.prototype = new baseClass();<BR> F.prototype.constructor = F;<BR> initializing = false;<BR> }<BR> // 覆盖父类的同名函数<BR> for (var name in prop) {<BR> if (prop.hasOwnProperty(name)) {<BR> // 如果此类继承自父类baseClass并且父类原型中存在同名函数name<BR> if (baseClass &&<BR> typeof (prop[name]) === "function" &&<BR> typeof (F.prototype[name]) === "function") {<br><br> // 重定义函数name - <BR> // 首先在函数上下文设置this.base指向父类原型中的同名函数<BR> // 然后调用函数prop[name],返回函数结果<br><br> // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,<BR> // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。<BR> // 这是JavaScript框架开发中常用的技巧。<BR> F.prototype[name] = (function(name, fn) {<BR> return function() {<BR> this.base = baseClass.prototype[name];<BR> return fn.apply(this, arguments);<BR> };<BR> })(name, prop[name]);<br><br> } else {<BR> F.prototype[name] = prop[name];<BR> }<BR> }<BR> }<BR> return F;<BR> };<BR>
var Person = jClass({<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> });<BR> var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> this.base(name);<BR> this.employeeID = employeeID;<BR> },<BR> getEmployeeID: function() {<BR> return this.employeeID;<BR> },<BR> getName: function() {<BR> return "Employee name: " + this.base();<BR> }<BR> });<br><br> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "Employee name: ZhangSan"<BR>
至此,我们已经创建了一个完善的函数jClass, 帮助我们在JavaScript中以比较优雅的方式实现类和继承。
在以后的章节中,我们会陆续分析网上一些比较流行的JavaScript类和继承的实现。 不过万变不离其宗,那些实现也无非把我们这章中提到的概念颠来簸去的“炒作”, 为的就是一种更优雅的调用方式。