JavaScript继承详解(五)

在本章中,我们将分析John Resig关于JavaScript继承的一个实现 – Simple JavaScript Inheritance。 John Resig作为jQuery的创始人而声名在外。是《Pro JavaScript Techniques》的作者,而且Resig将会在今年秋天推出一本书《JavaScript Secrets》,非常期待。

调用方式

调用方式非常优雅: 注意:代码中的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.Classthis.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() {// 在类的实例化时,调用原型方法initif (!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;};})();

如若今生再相见,哪怕流离百世,迷途千年,也愿。

JavaScript继承详解(五)

相关文章:

你感兴趣的文章:

标签云: