JavaScript 继承详解(五)

前端之家收集整理的这篇文章主要介绍了JavaScript 继承详解(五)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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

函数 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一样简洁明了。

代码分析

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

我曾在几天前的博客中写过一篇文章专门阐述这个问题,有兴趣可以向前翻一翻。

函数创建一个上下文,避免引入全局变量 (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循环进行解读,把自执行的匿名方法用一个局部函数来替换, 这样有利于我们看清真相:

// 如果<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>和子类有同名<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>,并且子类中此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>(name)通过_super<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>了<a href="/tag/fulei/" target="_blank" class="keywords">父类</a><a href="/tag/fangfa/" target="_blank" class="keywords">方法</a> // - 则重新定义此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a> function fn(name,fn) { return function() { // 将实例<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>_super保护起来。 // 个人觉得这个地方没有必要,因为每次<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>这样的<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>时都会对this._super重新定义。 var tmp = this._super; // 在执行子类的实例<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>name时,<a href="/tag/tianjia/" target="_blank" class="keywords">添加</a>另外一个实例<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>_super,此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>指向<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>的同名<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a> this._super = _super[name]; // 执行子类的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>name,注意在<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>体内this._super可以<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a><a href="/tag/fulei/" target="_blank" class="keywords">父类</a>的同名<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a> var ret = fn.apply(this,arguments); this._super = tmp; // 返回执行结果 return ret; }; } // 拷贝prop中的所有<a href="/tag/shuxing/" target="_blank" class="keywords">属性</a>到子类原型中 for (var name in prop) { // 如果prop和<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>中存在同名的<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>,并且此<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>中使用了_super<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>,则对此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>进行特殊处理 - fn // 否则将此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>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; }; })();</pre>

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

我们的实现

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

调用当前函数的对象(这里是函数)不是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;
    // 覆盖<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>的同名<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>
    for (var name in prop) {
      if (prop.hasOwnProperty(name)) {
        // 如果此类继承自<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>baseClass并且<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>原型中存在同名<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>name
        if (baseClass &&
        typeof (prop[name]) === "function" &&
        typeof (F.prototype[name]) === "function" &&
        /\b_super\b/.test(prop[name])) {
          // 重定义<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>name - 
          // 首先在<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>上下文设置this._super指向<a href="/tag/fulei/" target="_blank" class="keywords">父类</a>原型中的同名<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>
          // 然后<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a><a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>prop[name],返回<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>结果
          // 注意:这里的自执行<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>创建了一个上下文,这个上下文返回另一个<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>,
          // 此<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>中可以应用此上下文中的变量,这就是闭包(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) {
    // <a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a><a href="/tag/fulei/" target="_blank" class="keywords">父类</a>的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>
    this._super(name);
    this.employeeID = employeeID;
  },getEmployeeIDName: function() {
    // 注意:我们还可以通过这种方式<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a><a href="/tag/fulei/" target="_blank" class="keywords">父类</a>中的其他<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>
    var name = this._superprototype.getName.call(this,"Employee name: ");
    return name + ",Employee ID: " + this.employeeID;
  },getName: function() {
    // <a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a><a href="/tag/fulei/" target="_blank" class="keywords">父类</a>的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>
    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"</pre>

这篇文章就接受到这了,下面还有一个系列的文章,大家都可以看下

猜你在找的JavaScript相关文章