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

5.2 各种类工厂的实现

在第1节我们演示各种继承方式的实现,但都很凌乱。我们希望提供一个专门的方法,只要用户传入相应的参数或按一定的简单格式就能创建一个类,特别是子类。

由于主流框架的类工厂实现太依赖于它们庞杂的工具函数,而一个精巧的类工厂也不过百行左右,因此本章就不打算罗列 Prototype.js、Mootools 等的代码了,介绍另外一些不太出名但相当有水准的小库吧。

5.2.1 相当精巧的库——P.js

它的github地址为 https://github.com/jayferd/pjs。

这是一个相当于精巧的库,尤其在调用父类的同名方法时,它直接把父类的原型抛在你眼前,连_super也省了。

它的源码解读如下。

      var P = (function(prototype, ownProperty, undefined) {
          function isObject(o) {
              return typeof o === 'object';
          }
          function isFunction(f) {
              return typeof f === 'function';
          }
          function BareConstructor() {};
          function P(_superclass /* = Object */ , definition) {
              //如果只传一个参数,没有指定父类
              if(definition === undefined) {
                  definition = _superclass;
                  _superclass = Object;
              }
              //C为我们要返回的子类,definition中的init为用户构造器
              function C() {
                  var self = new Bare;
                  console.log(self.init)
                  if(isFunction(self.init)) self.init.apply(self, arguments);
                  return self;
              }
              function Bare() { //这个构造器是为了让C不用new就能返回实例而设的
              }
              C.Bare = Bare;
              //为了防止改动子类影响到父类,我们将父类的原型赋给一个中介者BareConstructor
              //然后再将这中介者的实例作为子类的原型
              var _super = BareConstructor[prototype] = _superclass[prototype];
              var proto = Bare[prototype] = C[prototype] = new BareConstructor; //
              //然后C与Bare都共享同一个原型
              //最后修正子类的构造器指向自身
              proto.constructor = C;
              //类方法mixin,不过def对象里面的属性与方法糅杂到原型里面去
              C.mixin = function(def) {
                  Bare[prototype] = C[prototype] = P(C, def)[prototype]; //Bare[prototype] =
                  return C;
              }
              //definition最后延迟到这里才起作用
              return(C.open = function(def) {
                  var extensions = {};
                  //definition有两种形态
                  //如果是函数,那么子类原型、父类原型、子类构造器、父类构造传进去,
                  //如果是对象则直接置为extensions
                  if(isFunction(def)) {
                      extensions = def.call(C, proto, _super, C, _superclass);
                  } else if(isObject(def)) {
                      extensions = def;
                  }
                  //最后混入子类的原型中
                  if(isObject(extensions)) {
                      for(var ext in extensions) {
                          if(ownProperty.call(extensions, ext)) {
                              proto[ext] = extensions[ext];
                          }
                      }
                  }
                  //确保init为一个函数
                  if(!isFunction(proto.init)) {
                      proto.init = _superclass;
                  }
                  return C;
              })(definition);
              //这里为一个自动执行函数表达式,相当于
              //C.open = function(){/*....*/}
              //C.open(definition)
              //return C;
              //换言之,返回的子类存在3个类成员,Base, mixin, open
          }
          return P; //暴露到全局
      })('prototype', ({}).hasOwnProperty);

我们尝试创建一个类。

      var Animal = P(function(proto, superProro) {
        proto.init = function(name) { //构造函数
          this.name = name;
        };
        proto.move = function(meters) { //原型方法
          console.log(this.name + " moved " + meters + "m.");
        }
      });
      var a = new Animal("aaa")
      var b = Animal("bbb");//无“new”实例化
      a.move(1)
      b.move(2)

当然在现在的情景下,我们可以使用更简洁的定义方式。

      var Snake = P(Animal, function(snake, animal) {
        snake.init = function(name, eyes) {
          animal.init.call(this, arguments); //调用父类构造器
          this.eyes = 2;
        }
        snake.move = function() {
          console.log("Slithering...");
          animal.move.call(this, 5); //调用父类的同名方法
        };
      });
      var s = new Snake("snake", 1);
      s.move();
      console.log(s.name);
      console.log(s.eyes);

下面是私有属性的演示,由于放在函数体内集中定义,因此安全可靠!

      var Cobra = P(Snake, function(cobra) {
        var age = 1; //私有属性
        //这里还可以编写私有方法
        cobra.glow = function() { //长大
          return age++;
        }
      });
      var c = new Cobra("cobra");
      console.log(c.glow()); //1
      console.log(c.glow()); //2 又长一岁
      console.log(c.glow()); //3 又长一岁

此外,它还提供了两个类方法,mixin用于再次添加新的原型成员,open的作用同mixin,但显然它适合于重写父类的方法(在子类方法内部重用父类方法),同时,也可以添加新的私有属性。open这个命名显然是受ruby影响,意为重新打开类,修改其原型。