`
xlongbuilder
  • 浏览: 41537 次
  • 性别: Icon_minigender_1
  • 来自: 上海->杭州
社区版块
存档分类
最新评论

(转)JavaScript继承详解(三)

阅读更多
var Employee = jClass(Person, {

init: function(name, employeeID) {

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

this.base(name);

this.employeeID = employeeID;

}

});

 在第一章中,我们使用构造函数和原型的方式在JavaScript的世界中实现了类和继承, 但是存在很多问题。这一章我们将会逐一分析这些问题,并给出解决方案。注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法。首先让我们来回顾一下第一章中介绍的例子:

 
function Person(name) {

this.name = name;

}

Person.prototype = {

getName: function() {

return this.name;

}

}



function Employee(name, employeeID) {

this.name = name;

this.employeeID = employeeID;

}

Employee.prototype = new Person();

Employee.prototype.getEmployeeID = function() {

return this.employeeID;

};

var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.getName()); // "ZhangSan" 
 

 

 

修正constructor的指向错误

 

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

 
var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.constructor === Employee); // false

console.log(zhang.constructor === Object); // true 


我们需要简单的修正:

 function Employee(name, employeeID) {

this.name = name;

this.employeeID = employeeID;

}

Employee.prototype = new Person();

Employee.prototype.constructor = Employee;

Employee.prototype.getEmployeeID = function() {

return this.employeeID;

};

var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.constructor === Employee); // true

console.log(zhang.constructor === Object); // false

 

 

 

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

 

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

 // 空的构造函数

function Person() {

}

Person.prototype = {

init: function(name) {

this.name = name;

},

getName: function() {

return this.name;

}

}

// 空的构造函数

function Employee() {

}

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

Employee.prototype = new Person();

Employee.prototype.constructor = Employee;

Employee.prototype.init = function(name, employeeID) {

this.name = name;

this.employeeID = employeeID;

};

Employee.prototype.getEmployeeID = function() {

return this.employeeID;

};
 

这种方式下,必须在实例化一个对象后手工调用init函数,如下:

 var zhang = new Employee();

zhang.init("ZhangSan", "1234");

console.log(zhang.getName()); // "ZhangSan"
 

 

 

如何自动调用init函数?

 

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

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

var initializing = false;

function Person() {

if (!initializing) {

this.init.apply(this, arguments);

}

}

Person.prototype = {

init: function(name) {

this.name = name;

},

getName: function() {

return this.name;

}

}

function Employee() {

if (!initializing) {

this.init.apply(this, arguments);

}

}

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

initializing = true;

Employee.prototype = new Person();

Employee.prototype.constructor = Employee;

initializing = false;

Employee.prototype.init = function(name, employeeID) {

this.name = name;

this.employeeID = employeeID;

};

Employee.prototype.getEmployeeID = function() {

return this.employeeID;

};



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

var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.getName()); // "ZhangSan"
 

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

 

 

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

 

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

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

var initializing = false;

function jClass(baseClass, prop) {

// 只接受一个参数的情况 - jClass(prop)

if (typeof (baseClass) === "object") {

prop = baseClass;

baseClass = null;

}

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

function F() {

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

if (!initializing) {

this.init.apply(this, arguments);

}

}

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

if (baseClass) {

initializing = true;

F.prototype = new baseClass();

F.prototype.constructor = F;

initializing = false;

}

// 覆盖父类的同名函数

for (var name in prop) {

if (prop.hasOwnProperty(name)) {

F.prototype[name] = prop[name];

}

}

return F;

};
 

使用jClass函数来创建类和继承类的方法:

 var Person = jClass({

init: function(name) {

this.name = name;

},

getName: function() {

return this.name;

}

});

var Employee = jClass(Person, {

init: function(name, employeeID) {

this.name = name;

this.employeeID = employeeID;

},

getEmployeeID: function() {

return this.employeeID;

}

});



var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.getName()); // "ZhangSan"
 

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

 

 

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

 

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

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

var initializing = false;

function jClass(baseClass, prop) {

// 只接受一个参数的情况 - jClass(prop)

if (typeof (baseClass) === "object") {

prop = baseClass;

baseClass = null;

}

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

function F() {

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

if (!initializing) {

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

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

if (baseClass) {

this.base = baseClass.prototype;

}

this.init.apply(this, arguments);

}

}

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

if (baseClass) {

initializing = true;

F.prototype = new baseClass();

F.prototype.constructor = F;

initializing = false;

}

// 覆盖父类的同名函数

for (var name in prop) {

if (prop.hasOwnProperty(name)) {

F.prototype[name] = prop[name];

}

}

return F;

};

 

调用方式:

 
var Person = jClass({

init: function(name) {

this.name = name;

},

getName: function() {

return this.name;

}

});

var Employee = jClass(Person, {

init: function(name, employeeID) {

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

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

this.employeeID = employeeID;

},

getEmployeeID: function() {

return this.employeeID;

},

getName: function() {

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

return "Employee name: " + this.base.getName.apply(this);

}

});



var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.getName()); // "Employee name: ZhangSan"
 

 

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

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

 

 

优化jClass函数

 

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

var initializing = false;

function jClass(baseClass, prop) {

// 只接受一个参数的情况 - jClass(prop)

if (typeof (baseClass) === "object") {

prop = baseClass;

baseClass = null;

}

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

function F() {

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

if (!initializing) {

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

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

if (baseClass) {

this.baseprototype = baseClass.prototype;

}

this.init.apply(this, arguments);

}

}

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

if (baseClass) {

initializing = true;

F.prototype = new baseClass();

F.prototype.constructor = F;

initializing = false;

}

// 覆盖父类的同名函数

for (var name in prop) {

if (prop.hasOwnProperty(name)) {

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

if (baseClass &&

typeof (prop[name]) === "function" &&

typeof (F.prototype[name]) === "function") {



// 重定义函数name - 

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

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



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

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

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

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

return function() {

this.base = baseClass.prototype[name];

return fn.apply(this, arguments);

};

})(name, prop[name]);



} else {

F.prototype[name] = prop[name];

}

}

}

return F;

};
 

此时,创建类与子类以及调用方式都显得非常优雅,请看:

 
var Person = jClass({

init: function(name) {

this.name = name;

},

getName: function() {

return this.name;

}

});

var Employee = jClass(Person, {

init: function(name, employeeID) {

this.base(name);

this.employeeID = employeeID;

},

getEmployeeID: function() {

return this.employeeID;

},

getName: function() {

return "Employee name: " + this.base();

}

});



var zhang = new Employee("ZhangSan", "1234");

console.log(zhang.getName()); // "Employee name: ZhangSan"
 

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

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

<!--endprint-->

分享到:
评论

相关推荐

    JavaScript 继承详解(三)

    在JavaScript编程中,继承是面向对象编程的核心概念之一,它允许开发者构建出层级化的对象结构。在JavaScript中实现继承有多种方法,其中构造函数和原型链的方式是较为传统的一种,但在实际应用中存在一些问题和陷阱...

    JavaScript继承详解.doc

    JavaScript中的继承是面向对象编程的重要概念,它允许一个对象(子对象)获取另一个对象(父对象)的属性和方法,从而实现代码复用和多态性。JavaScript支持多种继承实现方式,包括以下四种: 1. **构造函数继承**...

    JavaScript 继承详解(六)

    在本章中,我们将分析Prototypejs中关于JavaScript继承的实现。 Prototypejs是最早的JavaScript类库,可以说是JavaScript类库的鼻祖。 我在几年前接触的第一个JavaScript类库就是这位,因此Prototypejs有着广泛的...

    JavaScript 继承详解(二)

    总结一下,JavaScript的继承主要依赖于`prototype`、`constructor`和`this`这三个关键概念。`prototype`允许对象之间共享属性和方法,`constructor`标识对象的构造函数,而`this`则是动态绑定的上下文,用于确定当前...

    JavaScript 继承详解(四)

    Classical Inheritance in JavaScript。 Crockford是JavaScript开发社区最知名的...首先让我们看下使用Crockford式继承的调用方式: 注意:代码中的method、inherits、uber都是自定义的对象,我们会在后面的代码分析

    JavaScript面向对象继承详解

    5. **寄生组合继承**:结合寄生继承和组合继承,避免了冗余的构造函数调用,通常被认为是JavaScript中实现继承的最佳实践。 6. **ES6的类继承**:ES6引入了`class`语法糖,使得JavaScript的继承看起来更像传统的...

    详解Javascript继承的实现

    本文将深入探讨JavaScript继承的实现方式,以及其中的问题和解决方案。 首先,我们来看混合方式的实现,这种方式结合了原型链和对象冒充。在JavaScript中,构造函数是用于创建特定类型对象的函数。例如,`Employee`...

    JavaScript继承的特性与实践应用深入详解

    这里我们将深入探讨JavaScript继承的特性以及实践应用。 首先,JavaScript的继承基于原型链。每个对象都有一个`__proto__`属性,指向创建它的构造函数的原型对象。当试图访问对象的一个属性时,JavaScript会沿着...

    JavaScript中的原型和继承详解(图文)_.docx

    JavaScript 中的原型和继承详解 在 JavaScript 中,原型和继承是两个非常重要的概念。这篇文章将详细解释 JavaScript 中的原型和继承机制,并通过图文方式帮助读者更好地理解这些概念。 一、对象和类 在 ...

    javaScript使用详解.pdf

    JavaScript,是一种广泛应用于Web开发的轻量级编程语言,它主要负责实现客户端的动态...总结,《JavaScript使用详解》这本书将深入讲解这些内容,帮助读者从基础到进阶全面掌握JavaScript,为Web开发打下坚实的基础。

    javascript用函数实现继承详解javascript技巧docx.docx

    JavaScript中的继承机制是其面向对象编程的一个核心特性。在JavaScript中,可以使用多种方式来实现继承,其中之一就是通过函数来模拟继承。本文将详细介绍如何利用函数实现JavaScript的继承,并结合属性特性和描述符...

    【JavaScript源代码】JavaScript继承的三种方法实例.docx

    ### JavaScript继承的三种方法实例详解 #### 一、概述 在JavaScript中,虽然原生语言层面没有提供传统意义上的“类”这一概念,但它通过构造函数和原型链等机制实现了类的功能,尤其是继承这一核心概念。继承是...

    详解Javascript继承的实现_.docx

    本文将深入探讨JavaScript继承的实现,并分析其潜在的问题和解决方案。 首先,我们来看混合方式的实现,这是一种常见的继承策略,结合了原型链和构造函数继承。在JavaScript中,对象的属性和方法可以通过原型链进行...

    javascript闭包详解中文word版

    资源名称:javascript闭包详解 中文word版   内容简介: Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C 的程序员来说是一个新的...

    JavaScript继承与聚合实例详解

    本文实例讲述了JavaScript继承与聚合。分享给大家供大家参考,具体如下: 一、继承 第一种方式:类与被继承类直接耦合度高 1. 首先,准备一个可以被继承的类(父类),例如 //创建一个人员类 function Person(name)...

Global site tag (gtag.js) - Google Analytics