两个小巧的javascript类工厂

2015-07-31 jude 更多博文 » 博客 » GitHub »

javascript类

原文链接 http://judes.me/frontend/2015/07/31/JSClass.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


阅读前,希望你了解javascript的原型链

P.js

项目地址

基础用法:

var Animal = P(function(animal){
      animal.init = function(name){
          this.name = name;
      };
      animal.move = function(meters){
          console.log(this.name + " moved " + meters + " m.");
      }
  });

var Snake = P(Animal, function(snake, animal){
        snake.move = function(){
            console.log("Slithering...");
            animal.move.call(this, 5);
        };
    });

var Horse = P(Animal, function(horse, animal){
        horse.move = function(){
            console.log("Galloping...");
            animal.move.call(this, 45);
        };
    });

var sam = Snake("Sammy the Python"),
    tom = Horse("Tommy the Palomino");

sam.move(); 
// 输出 
// Slithering...
// Sammy the Python moved 5 m.
tom.move();
// 输出 
// Galloping...
// Tommy the Palomino moved 45 m.

源码

//说点题外话,作者利用闭包做了不少事情:

//传入变量 prototype 是为了能在使用代码压缩工具时缩短代码,例如把 prototype 替换为 p
//在压缩代码时 obj.prototype 和 obj[prototype] 是不一样的,前者的 prototype 不会被替换,所以要达到压缩变量的目的,还要配合后一种写法

//ownProperty 是 helper function

//传入 undefined 是阻止在使用 undefined 时回溯原型链,提高性能

(function(prototype, ownProperty, undefined) {
    //这个立即执行函数返回 类工厂 P
    //P接受两个参数, 分别对应 父类 和对父类的相关扩展
    //如果只传一个参数,就认为父类是 Object
    return function P(_superclass /* = Object */, definition) {
        // handle the case where no superclass is given
        if (definition === undefined) {
          definition = _superclass;
          _superclass = Object;
        }

        // C is the class to be returned.
        //
        // When called, creates and initializes an instance of C, unless
        // `this` is already an instance of C, then just initializes `this`;
        // either way, returns the instance of C that was initialized.
        //
        //  TODO: the Chrome inspector shows all created objects as `C`
        //        rather than `Object`.  Setting the .name property seems to
        //        have no effect.  Is there a way to override this behavior?

        // C 是 创建好类之后,实例化类时(不管有没有使用new)返回的对象,
        function C() {
            //如果没有使用 new ,则调用 new Bare
            var self = this instanceof C ? this : new Bare;
            self.init.apply(self, arguments);
            return self;
        }

        // C.Bare is a class with a noop constructor.  Its prototype will be
        // the same as C, so that instances of C.Bare are instances of C.
        // `new MyClass.Bare` then creates new instances of C without
        // calling .init().
        //    如果不想在实例化时调用 init() ,可以用 new C.Bare
        function Bare() {}
        C.Bare = Bare;

        // Extend the prototype chain: first use Bare to create an
        // uninitialized instance of the superclass, then set up Bare
        // to create instances of this class.

        //实现类继承,本质上都是用这样的手法:
        //    var TempClass = function(){}
        //    TempClass.prototype = Parent.prototype
        //    Child.prototype = new TempClass()

        var _super = Bare[prototype] = _superclass[prototype];
        var proto = Bare[prototype] = C[prototype] = C.p = new Bare;

        // pre-declaring the iteration variable for the loop below to save
        // a `var` keyword after minification
        // 把 key 声明为变量,方便压缩代码时替换
        var key;

        // set the constructor property on the prototype, for convenience
        //    修正 C 的构造函数,不修正的话会是 Bare
        proto.constructor = C;

        //    extend 返回一个子类
        C.extend = function(def) { return P(C, def); }

        //    这里写得比较复杂,化简一下会是这样 
        //    return (C.open = C)
        //    C.open 用于修改已存在的类的属性
        return (C.open = function(def) {
          if (typeof def === 'function') {
            // call the defining function with all the arguments you need
            // extensions captures the return value.

            //    def 接收的参数有4个
            //    proto为Bare的原型,即Object的原型
            //    _super为父类的原型
            //    C,_superclass

            //如果def返回对象,接下来会把此对象合并到proto里
            def = def.call(C, proto, _super, C, _superclass);
          }

          // ...and extend it
          if (typeof def === 'object') {
            for (key in def) {
              if (ownProperty.call(def, key)) {
                proto[key] = def[key];
              }
            }
          }

          // if no init, assume we're inheriting from a non-Pjs class, so
          // default to using the superclass constructor.
          if (!('init' in proto)) proto.init = _superclass;

          return C;
        })(definition);
    }

  // as a minifier optimization, we've closured in a few helper functions
  // and the string 'prototype' (C[p] is much shorter than C.prototype)
})('prototype', ({}).hasOwnProperty);

Simple JavaScript Inheritance

地址

用法

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});

var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});

var p = new Person(true);
p.dance(); // => true

var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true

// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class

源码

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){

    //fnTest 如果浏览器支持 Function.prototype.toString 打印自身内容,则匹配里面有没有 _super
    //        如果不支持,fnTest匹配结果为 true
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  // The base Class implementation (does nothing)
  this.Class = function(){};

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
      //先保存当前 Class 的原型
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    //    有一种实现继子的方式是 Child.prototype = new Parent()
    //    这种方式的好处是隔离了 Child.prototype 和 Parent.prototype
    //    坏处是调用了一次 Parent 的构造函数,构造函数中的一些初始化操作对子类来说是多余的
    //    开关 initializing 能控制在 new this() 时不进行初始化操作
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        //    如果在 proto 里有跟父类同名的函数,而且函数里面有 _super
        //    则用父类的同名函数覆盖 this._super
        (function(name, fn){
          return function() {
              //    this._super 不一定存在,但先保存下来肯定没错
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            //    作者假设 fn 里面调用了 this._super
            //    所以 fn 执行时会调用父类的同名函数
            var ret = fn.apply(this, arguments);        
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      // 在 extend 时不会调用 this.init
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    //    让子类也有 extend 方法
    //    在 es5 严格模式下,禁止使用 arguments.callee ,原因见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/callee
    //    可以使用命名函数解决此问题
    Class.extend = arguments.callee;

    return Class;
  };
})();