当前位置:首页 > JavaScript继承详解(三)

JavaScript继承详解(三)

点击次数:2074  更新日期:2011-01-05
\n

上一篇:JavaScript继承详解(二)


\n

在第一章中,我们使用构造函数和原型的方式在JavaScript的世界中实现了类和继承, 但是存在很多问题。这一章我们将会逐一分析这些问题,并给出解决方案。


\n

注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法。


\n

首先让我们来回顾一下第一章中介绍的例子:

 function Person(name) {

\nthis.name = name;

\n}

\nPerson.prototype = {

\ngetName: function() {

\nreturn this.name;

\n}

\n}

\n

\nfunction Employee(name, employeeID) {

\nthis.name = name;

\nthis.employeeID = employeeID;

\n}

\nEmployee.prototype = new Person();

\nEmployee.prototype.getEmployeeID = function() {

\nreturn this.employeeID;

\n};

\nvar zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.getName()); // “ZhangSan”

\n

\n

\n

修正constructor的指向错误

\n

从上一篇文章中关于constructor的描述,我们知道Employee实例的constructor会有一个指向错误,如下所示:

 var zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.constructor === Employee); // false

\nconsole.log(zhang.constructor === Object); // true

\n
我们需要简单的修正:
 function Employee(name, employeeID) {

\nthis.name = name;

\nthis.employeeID = employeeID;

\n}

\nEmployee.prototype = new Person();

\nEmployee.prototype.constructor = Employee;

\nEmployee.prototype.getEmployeeID = function() {

\nreturn this.employeeID;

\n};

\nvar zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.constructor === Employee); // true

\nconsole.log(zhang.constructor === Object); // false

\n

\n

\n

创建Employee类时实例化Person是不合适的

\n

但另一方面,我们又必须依赖于这种机制来实现继承。 解决办法是不在构造函数中初始化数据,而是提供一个原型方法(比如init)来初始化数据。

 // 空的构造函数

\nfunction Person() {

\n}

\nPerson.prototype = {

\ninit: function(name) {

\nthis.name = name;

\n},

\ngetName: function() {

\nreturn this.name;

\n}

\n}

\n// 空的构造函数

\nfunction Employee() {

\n}

\n// 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数

\nEmployee.prototype = new Person();

\nEmployee.prototype.constructor = Employee;

\nEmployee.prototype.init = function(name, employeeID) {

\nthis.name = name;

\nthis.employeeID = employeeID;

\n};

\nEmployee.prototype.getEmployeeID = function() {

\nreturn this.employeeID;

\n};

\n
这种方式下,必须在实例化一个对象后手工调用init函数,如下:
 var zhang = new Employee();

\nzhang.init(“ZhangSan”, “1234″);

\nconsole.log(zhang.getName()); // “ZhangSan”

\n

\n

\n

如何自动调用init函数?

\n

必须达到两个效果,构造类时不要调用init函数和实例化对象时自动调用init函数。看来我们需要在调用空的构造函数时有一个状态标示。

 // 创建一个全局的状态标示 – 当前是否处于类的构造阶段

\nvar initializing = false;

\nfunction Person() {

\nif (!initializing) {

\nthis.init.apply(this, arguments);

\n}

\n}

\nPerson.prototype = {

\ninit: function(name) {

\nthis.name = name;

\n},

\ngetName: function() {

\nreturn this.name;

\n}

\n}

\nfunction Employee() {

\nif (!initializing) {

\nthis.init.apply(this, arguments);

\n}

\n}

\n// 标示当前进入类的创建阶段,不会调用init函数

\ninitializing = true;

\nEmployee.prototype = new Person();

\nEmployee.prototype.constructor = Employee;

\ninitializing = false;

\nEmployee.prototype.init = function(name, employeeID) {

\nthis.name = name;

\nthis.employeeID = employeeID;

\n};

\nEmployee.prototype.getEmployeeID = function() {

\nreturn this.employeeID;

\n};

\n

\n// 初始化类实例时,自动调用类的原型函数init,并向init中传递参数

\nvar zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.getName()); // “ZhangSan”

\n
但是这样就必须引入全局变量,这是一个不好的信号。

\n

\n

如何避免引入全局变量initializing?

\n

我们需要引入一个全局的函数来简化类的创建过程,同时封装内部细节避免引入全局变量。

 // 当前是否处于创建类的阶段

\nvar initializing = false;

\nfunction jClass(baseClass, prop) {

\n// 只接受一个参数的情况 – jClass(prop)

\nif (typeof (baseClass) === “object”) {

\nprop = baseClass;

\nbaseClass = null;

\n}

\n// 本次调用所创建的类(构造函数)

\nfunction F() {

\n// 如果当前处于实例化类的阶段,则调用init原型函数

\nif (!initializing) {

\nthis.init.apply(this, arguments);

\n}

\n}

\n// 如果此类需要从其它类扩展

\nif (baseClass) {

\ninitializing = true;

\nF.prototype = new baseClass();

\nF.prototype.constructor = F;

\ninitializing = false;

\n}

\n// 覆盖父类的同名函数

\nfor (var name in prop) {

\nif (prop.hasOwnProperty(name)) {

\nF.prototype[name] = prop[name];

\n}

\n}

\nreturn F;

\n};

\n
使用jClass函数来创建类和继承类的方法:
 var Person = jClass({

\ninit: function(name) {

\nthis.name = name;

\n},

\ngetName: function() {

\nreturn this.name;

\n}

\n});

\nvar Employee = jClass(Person, {

\ninit: function(name, employeeID) {

\nthis.name = name;

\nthis.employeeID = employeeID;

\n},

\ngetEmployeeID: function() {

\nreturn this.employeeID;

\n}

\n});

\n

\nvar zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.getName()); // “ZhangSan”

\n
OK,现在创建类和实例化类的方式看起来优雅多了。 但是这里面还存在明显的瑕疵,Employee的初始化函数init无法调用父类的同名方法。

\n

\n

如何调用父类的同名方法?

\n

我们可以通过为实例化对象提供一个base的属性,来指向父类(构造函数)的原型,如下:

 // 当前是否处于创建类的阶段

\nvar initializing = false;

\nfunction jClass(baseClass, prop) {

\n// 只接受一个参数的情况 – jClass(prop)

\nif (typeof (baseClass) === “object”) {

\nprop = baseClass;

\nbaseClass = null;

\n}

\n// 本次调用所创建的类(构造函数)

\nfunction F() {

\n// 如果当前处于实例化类的阶段,则调用init原型函数

\nif (!initializing) {

\n// 如果父类存在,则实例对象的base指向父类的原型

\n// 这就提供了在实例对象中调用父类方法的途径

\nif (baseClass) {

\nthis.base = baseClass.prototype;

\n}

\nthis.init.apply(this, arguments);

\n}

\n}

\n// 如果此类需要从其它类扩展

\nif (baseClass) {

\ninitializing = true;

\nF.prototype = new baseClass();

\nF.prototype.constructor = F;

\ninitializing = false;

\n}

\n// 覆盖父类的同名函数

\nfor (var name in prop) {

\nif (prop.hasOwnProperty(name)) {

\nF.prototype[name] = prop[name];

\n}

\n}

\nreturn F;

\n};

\n
调用方式:
 var Person = jClass({

\ninit: function(name) {

\nthis.name = name;

\n},

\ngetName: function() {

\nreturn this.name;

\n}

\n});

\nvar Employee = jClass(Person, {

\ninit: function(name, employeeID) {

\n// 调用父类的原型函数init,注意使用apply函数修改init的this指向

\nthis.base.init.apply(this, [name]);

\nthis.employeeID = employeeID;

\n},

\ngetEmployeeID: function() {

\nreturn this.employeeID;

\n},

\ngetName: function() {

\n// 调用父类的原型函数getName

\nreturn “Employee name: ” + this.base.getName.apply(this);

\n}

\n});

\n

\nvar zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.getName()); // “Employee name: ZhangSan”

\n

\n

目前为止,我们已经修正了在第一章手工实现继承的种种弊端。 通过我们自定义的jClass函数来创建类和子类,通过原型方法init初始化数据, 通过实例属性base来调用父类的原型函数。


\n

唯一的缺憾是调用父类的代码太长,并且不好理解, 如果能够按照如下的方式调用岂不是更妙:

 var Employee = jClass(Person, {

\ninit: function(name, employeeID) {

\n// 如果能够这样调用,就再好不过了

\nthis.base(name);

\nthis.employeeID = employeeID;

\n}

\n});

\n

\n

优化jClass函数


\n
 // 当前是否处于创建类的阶段

\nvar initializing = false;

\nfunction jClass(baseClass, prop) {

\n// 只接受一个参数的情况 – jClass(prop)

\nif (typeof (baseClass) === “object”) {

\nprop = baseClass;

\nbaseClass = null;

\n}

\n// 本次调用所创建的类(构造函数)

\nfunction F() {

\n// 如果当前处于实例化类的阶段,则调用init原型函数

\nif (!initializing) {

\n// 如果父类存在,则实例对象的baseprototype指向父类的原型

\n// 这就提供了在实例对象中调用父类方法的途径

\nif (baseClass) {

\nthis.baseprototype = baseClass.prototype;

\n}

\nthis.init.apply(this, arguments);

\n}

\n}

\n// 如果此类需要从其它类扩展

\nif (baseClass) {

\ninitializing = true;

\nF.prototype = new baseClass();

\nF.prototype.constructor = F;

\ninitializing = false;

\n}

\n// 覆盖父类的同名函数

\nfor (var name in prop) {

\nif (prop.hasOwnProperty(name)) {

\n// 如果此类继承自父类baseClass并且父类原型中存在同名函数name

\nif (baseClass &&

\ntypeof (prop[name]) === “function” &&

\ntypeof (F.prototype[name]) === “function”) {

\n

\n// 重定义函数name –

\n// 首先在函数上下文设置this.base指向父类原型中的同名函数

\n// 然后调用函数prop[name],返回函数结果

\n

\n// 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,

\n// 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。

\n// 这是JavaScript框架开发中常用的技巧。

\nF.prototype[name] = (function(name, fn) {

\nreturn function() {

\nthis.base = baseClass.prototype[name];

\nreturn fn.apply(this, arguments);

\n};

\n})(name, prop[name]);

\n

\n} else {

\nF.prototype[name] = prop[name];

\n}

\n}

\n}

\nreturn F;

\n};

\n
此时,创建类与子类以及调用方式都显得非常优雅,请看:
 var Person = jClass({

\ninit: function(name) {

\nthis.name = name;

\n},

\ngetName: function() {

\nreturn this.name;

\n}

\n});

\nvar Employee = jClass(Person, {

\ninit: function(name, employeeID) {

\nthis.base(name);

\nthis.employeeID = employeeID;

\n},

\ngetEmployeeID: function() {

\nreturn this.employeeID;

\n},

\ngetName: function() {

\nreturn “Employee name: ” + this.base();

\n}

\n});

\n

\nvar zhang = new Employee(“ZhangSan”, “1234″);

\nconsole.log(zhang.getName()); // “Employee name: ZhangSan”

\n

\n

至此,我们已经创建了一个完善的函数jClass, 帮助我们在JavaScript中以比较优雅的方式实现类和继承。


\n

在以后的章节中,我们会陆续分析网上一些比较流行的JavaScript类和继承的实现。 不过万变不离其宗,那些实现也无非把我们这章中提到的概念颠来簸去的“炒作”, 为的就是一种更优雅的调用方式。


\n

来源:http://www.cnblogs.com/sanshi

\n