JavaScript框架设计
上QQ阅读APP看书,第一时间看更新

5.2.3 simple-inheritance

作者为大名鼎鼎的John Resig,项目直接放在他的一篇博文中:

http://ejohn.org/blog/simple-javascript-inheritance/

特点是方法链的实现非常优雅,节俭!

源码解读如下。

      (function() {
          // /xyz/.test(function(){xyz;})是用于判定函数的toString是否能暴露里面的实现
          // 因为Function.prototype.toString没有做出强制规定如何显示自身,根据浏览器实现而定
          // 如果里面能显示内部内容,那么我们就使用 /\b_super\b/来检测函数里面有没有.super语句
          // 当然这个也不很充分,只是够用的程度;否则就返回一个怎么也返回true的正则
          // 比如一些古老版本的Safari、Mobile Opera与 Blackberry浏览器,无法显示函数体的内容
          // 就需要用到后面的正则
          var initializing = false, fnTest = /xyz/.test(function() {
              xyz;
          }) ? /\b_super\b/ : /.*/;
          // 所有人工类的基类
          this.Class = function() {
          };
      //这是用于生成目标类的子类
          Class.extend = function(prop) {
              var _super = this.prototype;//保存父类的原型
              //阻止init被触发
              initializing = true;
              var prototype = new this();//创建子类的原型
              //重新打开,方便真实用户可以调用init
              initializing = false;
              //将prop里的东西逐个复制到prototype,如果是函数将特殊处理一下
              //因为复制过程中可能掩盖了超类的同名方法,如果这个函数里面存在_super的字样,就笼统地
              //认为它需要调用父类的同名方法,那么我们需要重写当前函数
              //重写函数运用了闭包,因此fnTest正则检测可以减少我们重写方法的个数,
              //因为不是每个同名函数都会向上调用父类方法
              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;//保存到临时变量中
                                  //当我们调用时,才匆匆把父类的同方法覆写到_super里
                                  this._super = _super[name];
                                  //然后才开始执行当前方法(这时里面的this._super已被重写),得到想要的效果
                                  var ret = fn.apply(this, arguments);
                                  //还原this._super
                                  this._super = tmp;
                                  //返回结果
                                  return ret;
                              };
                          })(name, prop[name]) :
                          prop[name];
              }
              // 这是目标类的真实构造器
              function Class() {
                  // 为了防止在生成子类的原型(new this())时触发用户传入的构造器init
                  // 使用initializing进行牵制
                  if (!initializing && this.init)
                      this.init.apply(this, arguments);
              }
              //将修改好的原型赋值
              Class.prototype = prototype;
              // 确保原型上constructor正确指向自身
              Class.prototype.constructor = Class;
              //添加extend类方法,生于生产它的子类
              Class.extend = arguments.callee;
              return Class;
          };
      })();

创建一个Animal类与一个Dog子类。

      var Animal = Class.extend({
        init: function(name) {
          this.name = name;
        },
        shout: function(s) {
          console.log(s);
        }
      });
      var animal = new Animal();
      animal.shout('animal'); // animal
      var Dog = Animal.extend({
        init: function(name, age) {
          //调用父类构造器
          this._super.apply(this, arguments);
          this.age = age;
        },
        run: function(s) {
          console.log(s);
        }
      });
      var dog = new Dog("dog", 4);
      console.log(dog.name); //dog
      dog.shout("xxx"); // xxx
      dog.run("run"); // run
      console.log(dog instanceof Dog && dog instanceof Animal);

顺便一提,simple-inheritance的老师有两个,Base2的继承系统与Prototype.js的方法链系统。Prototype.js与simple-inheritance都是对函数的toString进行反编译,看里面有没有_super或$super的字眼才决定重写。不同的是Prototype.js只检测方法的参数列表,因此杂质更少,更加可靠!

下面是Prototype.js方法链演示。

      var Person = Class.create();
      Person.prototype = {
        initialize: function(name) {
          this.name = name;
        },
        say: function(message) {
          return this.name + ': ' + message;
        }
      };
      var guy = new Person('Miro');
      console.log(guy.say('hi')); // "Miro: hi"
      //创建子类
      var Pirate = Class.create(Person, {
        say: function($super, message) { //注意这里的传参,$super为超类的同名方法
          return $super(message) + ", yarr!"; //这需要外科手术般的闭包来实现
        }
      });
      var john = new Pirate('Long John');
      console.log(john.say('good bye')) //Long John: good bye, yarr!

当然Prototype.js这样做有个缺憾,导致定义时与使用时的参数不一样。对于大多数用户来说,实现并不是他们所关心的,保持简洁优雅的接口才是重点。如果翻看jQuery UI的源码,它的widget.类工厂已经把方法链设计得登峰造顶了。它会在函数的 this 对象添加两个临时方法,_super 相当于simple-inheritance的_super,它的参数需要用户逐个转手传递。_superApply是_super的强化版,因为如果外围方法是不确定的,那么你也没法为_super传参,因此它只需用户丢个arguments对象进去!