网络编程 
首页 > 网络编程 > 浏览文章

JavaScript 继承详解(五)

(编辑:jimmy 日期: 2024/11/19 浏览:3 次 )

在本章中,我们将分析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;}) "htmlcode">
// 自执行的匿名函数创建一个上下文,避免引入全局变量
(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; }) "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) "htmlcode">
(function() {
      var initializing = false, fnTest = /xyz/.test(function() { xyz; }) "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"

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

上一篇:JavaScript 继承详解(六)
下一篇:Javascript动画效果(4)
荣耀猎人回归!七大亮点看懂不只是轻薄本,更是游戏本的MagicBook Pro 16.
人们对于笔记本电脑有一个固有印象:要么轻薄但性能一般,要么性能强劲但笨重臃肿。然而,今年荣耀新推出的MagicBook Pro 16刷新了人们的认知——发布会上,荣耀宣布猎人游戏本正式回归,称其继承了荣耀 HUNTER 基因,并自信地为其打出“轻薄本,更是游戏本”的口号。
众所周知,寻求轻薄本的用户普遍更看重便携性、外观造型、静谧性和打字办公等用机体验,而寻求游戏本的用户则普遍更看重硬件配置、性能释放等硬核指标。把两个看似难以相干的产品融合到一起,我们不禁对它产生了强烈的好奇:作为代表荣耀猎人游戏本的跨界新物种,它究竟做了哪些平衡以兼顾不同人群的各类需求呢?