在本章中,我们将分析John Resig关于JavaScript继承的一个实现 - Simple JavaScript Inheritance。 
John Resig作为jQuery的创始人而声名在外。是《Pro JavaScript Techniques》的作者。

调用方式

调用方式非常优雅: 
注意:代码中的Class、extend、_super都是自定义的对象,我们会在后面的代码分析中详解。

var Person = Class.extend({
// init是构造函数
init: function(name) {
this.name = name;
},
getName: function() {
return this.name;
}
});
// Employee类从Person类继承
var Employee = Person.extend({
// init是构造函数
init: function(name, employeeID) {
// 在构造函数中调用父类的构造函数
this._super(name);
this.employeeID = employeeID;
},
getEmployeeID: function() {
return this.employeeID;
},
getName: function() {
// 调用父类的方法
return "Employee name: " + this._super();
}
}); var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.getName()); // "Employee name: ZhangSan"

说实话,对于完成本系列文章的目标-继承-而言,真找不到什么缺点。方法一如jQuery一样简洁明了。

代码分析

为了一个漂亮的调用方式,内部实现的确复杂了很多,不过这些也是值得的 - 一个人的思考带给了无数程序员快乐的微笑 - 嘿嘿,有点肉麻。 
不过其中的一段代码的确迷惑我一段时间:

fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 

// 自执行的匿名函数创建一个上下文,避免引入全局变量
(function() {
// initializing变量用来标示当前是否处于类的创建阶段,
// - 在类的创建阶段是不能调用原型方法init的
// - 我们曾在本系列的第三篇文章中详细阐述了这个问题
// fnTest是一个正则表达式,可能的取值为(/\b_super\b/ 或 /.*/)
// - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的情况
// - 不过我对IE7.0,Chrome2.0,FF3.5进行了测试,此测试都返回true。
// - 所以我想这样对fnTest赋值大部分情况下也是对的:fnTest = /\b_super\b/;
var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
// 基类构造函数
// 这里的this是window,所以这整段代码就向外界开辟了一扇窗户 - window.Class
this.Class = function() { };
// 继承方法定义
Class.extend = function(prop) {
// 这个地方很是迷惑人,还记得我在本系列的第二篇文章中提到的么
// - this具体指向什么不是定义时能决定的,而是要看此函数是怎么被调用的
// - 我们已经知道extend肯定是作为方法调用的,而不是作为构造函数
// - 所以这里this指向的不是Object,而是Function(即是Class),那么this.prototype就是父类的原型对象
// - 注意:_super指向父类的原型对象,我们会在后面的代码中多次碰见这个变量
var _super = this.prototype;
// 通过将子类的原型指向父类的一个实例对象来完成继承
// - 注意:this是基类构造函数(即是Class)
initializing = true;
var prototype = new this();
initializing = false;
// 我觉得这段代码是经过作者优化过的,所以读起来非常生硬,我会在后面详解
for (var name in prop) {
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn) {
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// 这个地方可以看出,Resig很会伪装哦
// - 使用一个同名的局部变量来覆盖全局变量,很是迷惑人
// - 如果你觉得拗口的话,完全可以使用另外一个名字,比如function F()来代替function Class()
// - 注意:这里的Class不是在最外层定义的那个基类构造函数
function Class() {
// 在类的实例化时,调用原型方法init
if (!initializing && this.init)
this.init.apply(this, arguments);
}
// 子类的prototype指向父类的实例(完成继承的关键)
Class.prototype = prototype;
// 修正constructor指向错误
Class.constructor = Class;
// 子类自动获取extend方法,arguments.callee指向当前正在执行的函数
Class.extend = arguments.callee;
return Class;
};
})();

下面我会对其中的for-in循环进行解读,把自执行的匿名方法用一个局部函数来替换, 这样有利于我们看清真相:

(function() {
var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
this.Class = function() { };
Class.extend = function(prop) {
var _super = this.prototype;
initializing = true;
var prototype = new this();
initializing = false; // 如果父类和子类有同名方法,并且子类中此方法(name)通过_super调用了父类方法
// - 则重新定义此方法
function fn(name, fn) {
return function() {
// 将实例方法_super保护起来。
// 个人觉得这个地方没有必要,因为每次调用这样的函数时都会对this._super重新定义。
var tmp = this._super;
// 在执行子类的实例方法name时,添加另外一个实例方法_super,此方法指向父类的同名方法
this._super = _super[name];
// 执行子类的方法name,注意在方法体内this._super可以调用父类的同名方法
var ret = fn.apply(this, arguments);
this._super = tmp; // 返回执行结果
return ret;
};
}
// 拷贝prop中的所有属性到子类原型中
for (var name in prop) {
// 如果prop和父类中存在同名的函数,并且此函数中使用了_super方法,则对此方法进行特殊处理 - fn
// 否则将此方法prop[name]直接赋值给子类的原型
if (typeof prop[name] === "function" &&
typeof _super[name] === "function" && fnTest.test(prop[name])) {
prototype[name] = fn(name, prop[name]);
} else {
prototype[name] = prop[name];
}
} function Class() {
if (!initializing && this.init) {
this.init.apply(this, arguments);
}
}
Class.prototype = prototype;
Class.constructor = Class;
Class.extend = arguments.callee;
return Class;
};
})(); 

写到这里,大家是否觉得Resig的实现和我们在第三章一步一步实现的jClass很类似。 其实在写这一系列的文章之前,我已经对prototype、mootools、extjs、 jQuery-Simple-Inheritance、Crockford-Classical-Inheritance这些实现有一定的了解,并且大部分都在实际项目中使用过。 在第三章中实现jClass也参考了Resig的实现,在此向Resig表示感谢。 
下来我们就把jClass改造成和这里的Class具有相同的行为。

我们的实现

将我们在第三章实现的jClass改造成目前John Resig所写的形式相当简单,只需要修改其中的两三行就行了:

(function() {
// 当前是否处于创建类的阶段
var initializing = false;
jClass = function() { };
jClass.extend = function(prop) {
// 如果调用当前函数的对象(这里是函数)不是Class,则是父类
var baseClass = null;
if (this !== jClass) {
baseClass = this;
}
// 本次调用所创建的类(构造函数)
function F() {
// 如果当前处于实例化类的阶段,则调用init原型函数
if (!initializing) {
// 如果父类存在,则实例对象的baseprototype指向父类的原型
// 这就提供了在实例对象中调用父类方法的途径
if (baseClass) {
this._superprototype = baseClass.prototype;
}
this.init.apply(this, arguments);
}
}
// 如果此类需要从其它类扩展
if (baseClass) {
initializing = true;
F.prototype = new baseClass();
F.prototype.constructor = F;
initializing = false;
}
// 新创建的类自动附加extend函数
F.extend = arguments.callee; // 覆盖父类的同名函数
for (var name in prop) {
if (prop.hasOwnProperty(name)) {
// 如果此类继承自父类baseClass并且父类原型中存在同名函数name
if (baseClass &&
typeof (prop[name]) === "function" &&
typeof (F.prototype[name]) === "function" &&
/\b_super\b/.test(prop[name])) {
// 重定义函数name -
// 首先在函数上下文设置this._super指向父类原型中的同名函数
// 然后调用函数prop[name],返回函数结果
// 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,
// 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。
// 这是JavaScript框架开发中常用的技巧。
F.prototype[name] = (function(name, fn) {
return function() {
this._super = baseClass.prototype[name];
return fn.apply(this, arguments);
};
})(name, prop[name]);
} else {
F.prototype[name] = prop[name];
}
}
}
return F;
};
})();
// 经过改造的jClass
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"

JUST COOL!

JavaScript继承详解(五)的更多相关文章

  1. [原创]JavaScript继承详解

    原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...

  2. JavaScript继承详解

    面向对象与基于对象 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例. 类定义了一类事物公共的行为和方法:而实例则是类的一个具体实现. 我们还知道,面向对象编程有三个重要的概念 - 封装.继 ...

  3. JavaScript继承详解(四)

    在本章中,我们将分析Douglas Crockford关于JavaScript继承的一个实现 - Classical Inheritance in JavaScript. Crockford是Java ...

  4. JavaScript继承详解(一)

    面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++.C#.Java)的开发经验. 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例. 类定义了一类事物公共的行为和方法:而实例则 ...

  5. 【转载】JavaScript继承详解(二)

    这一章我们将会重点介绍JavaScript中几个重要的属性(this.constructor.prototype), 这些属性对于我们理解如何实现JavaScript中的类和继承起着至关重要的作用. ...

  6. 【转载】JavaScript继承详解一

    面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++.C#.Java)的开发经验. 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例. 类定义了一类事物公共的行为和方法:而实例则 ...

  7. JavaScript继承详解(二)

    这一章我们将会重点介绍JavaScript中几个重要的属性(this.constructor.prototype), 这些属性对于我们理解如何实现JavaScript中的类和继承起着至关重要的作用. ...

  8. JavaScript继承详解(三)

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

  9. javascript继承详解(待续)

    常见继承分两种,一种接口继承,继承方法签名:一种实现继承,继承实际方法.js只支持后一种. 1原型链 首先看原型.构造函数.实例的关系.如果我们让一个函数的原型对象等于另一个的实例,然后另一个的原型对 ...

随机推荐

  1. 《unity 3D 游戏开发 第二版》宣雨松 分享 pdf下载

    链接:https://pan.baidu.com/s/1LfRTGUmaE_lGdcmd6QiZkg 提取码:e2sn

  2. 【翻译】给初学者的 Neural Networks / 神经网络 介绍

    本文翻译自 SATYA MALLICK 的  "Neural Networks : A 30,000 Feet View for Beginners" 原文链接: https:// ...

  3. [paper]MaskFusion: Real-Time Recognition, Tracking and Reconstruction of Multiple Moving Objects

    Before 近期在调研关于RGBD在室内移动机器人下的语义导航的研究.目前帝国理工的Andrew Davison在这边有两个团队在研究,分别是Fusion++ 和 这篇 MaskFusion.这篇是 ...

  4. ace how to guide

    Configuring the editor there are several ways to pass configuration to Ace 有几种方法可以将配置传递给ace // pass ...

  5. error: Build input file cannot be found: '*******/node_modules/react-native/Libraries/WebSocket/libfishhook.a' 问题解决记录

    解决了刚才的'config.h' file not found问题,本以为就可以顺畅的跑起来,谁知道又被恶心到了,Build input file cannot be found!!! 问题: err ...

  6. PAT甲题题解-1005. Spell It Right (20)-数位求和,水

    把每个位上的数字求和sum,然后以英文单词的形式输出sum的每个位 #include <iostream> #include <cstdio> #include <alg ...

  7. PAT甲题题解-1081. Rational Sum (20)-模拟分数计算

    模拟计算一些分数的和,结果以带分数的形式输出注意一些细节即可 #include <iostream> #include <cstdio> #include <algori ...

  8. 关于sql server2008数据库的连接的几个问题及解决办法

    写在开头 不得不说给一台新的服务器配置和部署的确是个不小的工程,在这里先感谢我们的DEV焉域政同学在这方面做出的一些贡献:把安装过程极为困难的sql server2008成功安装到服务器上,并且为我们 ...

  9. [2017BUAA软工助教]团队beta得分总表

    一.累计得分 项目 α例会 α发布 α测试 α展示 α事后 合计 满分 50 10 10 150 10 230 hotcode5 50 10 9 150 9 228 弗朗明哥舞步 50 10 8 13 ...

  10. 关于cocos2dx 关键字的问题

    今天码代码,在创建新场景的时候,.h文件里  class Game : public cocos2d::Layer没有问题,在Game类里面,声明了它的成员之后,开始在.cpp文件里面实现这个类,到重 ...