javascript物件導向包裝類Class的類庫解析

iteye_3829發表於2013-05-22

javascript是個入門門檻很低的語言,甚至一個從來沒有接觸過javascript的技術人員,幾小時內就可以寫出一個簡單有用的程式程式碼。

但是如果因此你就下結論:javascript是門簡單的語言。那你就大錯特錯了。想寫出高效能的程式碼,同樣需要具備一個高階程式設計師的基本素養。

一個java或者c++程式設計師,不一定能寫出高效能的javascript程式碼,但更容易寫出高效能的javascript程式碼。

javascript的簡單是基於它“胸襟廣闊”的包容性。它宣告時,不需要指定型別,甚至可以任意的轉換型別。它物件導向,卻沒有類(Class)的限制。它是一門崇尚自由又非常嚴謹的語言,如果你是一個自由主義者,那麼,擁抱javascript吧!

物件導向程式設計 (OOP) 是一種流行的程式設計方法。但javascript的OOP,較之JAVA、c++有很大的同,主要體現它的繼承方式不同。javascript是基於原型PROTOTYPE繼承的。所有物件都是基於原型鏈,最終追述到Object物件。

這裡不想討論過多的關於javascript的繼承方式和其它語言的繼承方式的不同之處。主要討論如何封裝javascript的Class,以便更好的管理和維護基礎程式碼,減少重複程式碼,以及更好的模組化程式設計。

 

下面是幾個github上找到的比較好的Class封裝類庫:

  一、MY-CLASS 

  專案地址:https://github.com/jiem/my-class

先看基本用法:

a、新建一個類

(function() {
  // 新建類
  var Person = my.Class({
    // 新增靜態方法
    STATIC: {
      AGE_OF_MAJORITY: 18
    },
    // 建構函式
    constructor: function(name, age) {
      this.name = name;
      this.age = age;
    },
    // 例項方法
    sayHello: function() {
      console.log('Hello from ' + this.name + '!');
    },
    // 例項方法
    drinkAlcohol: function() {
      this.age < Person.AGE_OF_MAJORITY ?
        console.log('Too young! Drink milk instead!') :
        console.log('Whiskey or beer?');
    }

  });
  // 暴露給名稱空間
  myLib.Person = Person;

})();

var john = new myLib.Person('John', 16);
john.sayHello(); //log "Hello from John!"
john.drinkAlcohol(); //log "Too young! Drink milk instead!"

b、繼承一個類

(function() {

  //Dreamer 繼承 Person
  var Dreamer = my.Class(Person, {
    // 構造方法
    constructor: function(name, age, dream) {
      Dreamer.Super.call(this, name, age);
      this.dream = dream;
    },
    // 例項方法
    sayHello: function() {
      superSayHello.call(this);
      console.log('I dream of ' + this.dream + '!');
    },
    // 例項方法
    wakeUp: function() {
      console.log('Wake up!');
    }

  });
  // Super訪問父類
  var superSayHello = Dreamer.Super.prototype.sayHello;
  // 暴露給全域性名稱空間
  myLib.Dreamer = Dreamer;

})();

var sylvester = new myLib.Dreamer('Sylvester', 30, 'eating Tweety');
sylvester.sayHello(); //log "Hello from Sylvester! I dream of eating Tweety!"
sylvester.wakeUp(); //log "Wake up!"

c、給類新增新方法

// 給myLib.Dreamer新增新方法
my.extendClass(myLib.Dreamer, {
  // 新增靜態方法
  STATIC : {
    s_dongSomeThing : function(){
        console.log("do some thing!");    
    }
  },
  // 新增例項方法
  touchTheSky: function() {
    console.log('Touching the sky');
  },
  // 新增例項方法
  reachTheStars: function() {
    console.log('She is so pretty!');
  }

});

 d、實現一個類的方法

// 宣告一個新類
myLib.ImaginaryTraveler = my.Class({
  travel: function() { console.log('Traveling on a carpet!'); },
  crossOceans: function() { console.log('Saying hi to Moby Dick!'); }
});

(function() {

  //Dreamer 繼承 Person 實現 ImaginaryTraveler的方法
  var Dreamer = my.Class(Person, ImaginaryTraveler, {
    // 構造方法
    constructor: function(name, age, dream) {
      Dreamer.Super.call(this, name, age);
      this.dream = dream;
    }

   // ...

  });
  // 暴露給全域性名稱空間
  myLib.Dreamer = Dreamer;

})();

var aladdin = new Dreamer('Aladdin');
aladdin instanceof Person; //true
aladdin instanceof ImaginaryTraveler; //false
aladdin.travel();
aladdin.wakeUp();
aladdin.sayHello();

如果怕忘記new操作符

var Person = my.Class({

  //you can now call the constructor with or without new
  constructor: function(name, city) {
    if (!(this instanceof Person))
      return new Person(name, city);
    this.name = name;
    this.city = citye;
  }

});

下面看一下my.class的原始碼解析:

my.Class實現思路基本是這樣的,如果只有一個引數,那麼宣告的是一個基礎類,這個引數是用來宣告新類的方法和屬以及建構函式。它不是繼承而來,但它可以被繼承。

繼承的思路,就是如果有兩個引數,第一個引數做為父類被繼承,第二引數用來宣告新類的方法和屬性以及建構函式,它同樣可以被繼承。

如果有三個以上引數那麼,除出第一個引數做為繼承的父類,最後一個引數用宣告新類的方法和屬性以及建構函式。中間的引數是用類來擴充套件新類的方法。當然也可以通過my.extendClass擴充套件新方法。

同時,類庫為commonJS和瀏覽環境都提供了支援!

/*globals define:true, window:true, module:true*/
(function () {
  // Namespace object
  var my = {};
  // 保證AMD分模組可用
  if (typeof define !== 'undefined')
    define([], function () {
      return my;
    });
  else if (typeof window !== 'undefined')
    // 保證客戶端可用
    window.my = my;
  else
    // 保證後臺可用
    module.exports = my;

  //============================================================================
  // @method my.Class
  // @params body:Object
  // @params SuperClass:function, ImplementClasses:function..., body:Object
  // @return function
  my.Class = function () {

    var len = arguments.length;
    var body = arguments[len - 1];    // 最後一個引數是指定本身的方法
    var SuperClass = len > 1 ? arguments[0] : null;     // 第一個引數是指繼承的方法,例項和靜態部分均繼承
    var hasImplementClasses = len > 2;    // 如果有第三個引數,那麼第二個就是implementClass,這裡其實只繼承例項物件
    var Class, SuperClassEmpty;
    
    // 保證構造方法
    if (body.constructor === Object) {
      Class = function() {};
    } else {
      Class = body.constructor;
      // 保證後面不覆蓋constructor
      delete body.constructor;
    }
    // 處理superClass部分
    if (SuperClass) {
      // 中介軟體實現例項屬性的繼承
      SuperClassEmpty = function() {};
      SuperClassEmpty.prototype = SuperClass.prototype;
      Class.prototype = new SuperClassEmpty();    // 原型繼承,解除引用
      Class.prototype.constructor = Class;    // 保證constructor
      Class.Super = SuperClass;    // 父物件訪問介面
      
      // 靜態方法繼承,過載superClass方法
      extend(Class, SuperClass, false);
    }
    
    // 處理ImplementClass部分,其實只繼承例項屬性部分,除SuperClass #arguments[0]# 和 body #arguments[length-1]#
    if (hasImplementClasses)
      for (var i = 1; i < len - 1; i++)
        // implement是繼承的例項屬性部分, 過載父物件implementClass方法
        extend(Class.prototype, arguments[i].prototype, false);    
    
    // 處理本身宣告body部分,靜態要STATIC指定,例項部分要刪除STATIC部分
    extendClass(Class, body);
    
    return Class;

  };

  //============================================================================
  // @method my.extendClass
  // @params Class:function, extension:Object, ?override:boolean=true
  var extendClass = my.extendClass = function (Class, extension, override) {
    // 靜態部分繼承靜態部分
    if (extension.STATIC) {
      extend(Class, extension.STATIC, override);
      // 保證例項部分不繼承靜態方法
      delete extension.STATIC;
    }
    // 例項屬性繼繼承例項部
    extend(Class.prototype, extension, override);
  };

  //============================================================================
  var extend = function (obj, extension, override) {
    var prop;
    // 其實這裡的flase是表明,覆蓋父物件的方法
    if (override === false) {
      for (prop in extension)
        if (!(prop in obj))
          obj[prop] = extension[prop];
    } else {
       // 這裡其實不覆蓋父物件的方法,包括toString
      for (prop in extension)
        obj[prop] = extension[prop];
      if (extension.toString !== Object.prototype.toString)
        obj.toString = extension.toString;
    }
  };

})();

 

 二、KLASS

 專案地址:https://github.com/ded/klass

先看使用方法:

a、新建一個類

// 宣告一個類
var Person = klass(function (name) {
  this.name = name
})
  .statics({//靜態方法
    head: ':)',
    feet: '_|_'
  })
  .methods({//例項方法
    walk: function () {}
  })

b、繼承一個類

// SuperHuman 繼承 Person
var SuperHuman = Person.extend(function (name) {
  // 自動呼叫父類的構造方法
})
  .methods({
    walk: function() {
      // 顯式宣告呼叫父類的walk方法
      this.supr()
      this.fly()
    },

    fly: function() {}

  })

new SuperHuman('Zelda').walk()

c、字面量方式宣告一個類

var Foo = klass({
  foo: 0,
  initialize: function() {
    this.foo = 1
  },
  getFoo: function () {
    return this.foo
  },
  setFoo: function (x) {
    this.foo = x
    return this.getFoo()
  }
})

d、實現一個類的方法

因為有時候你可能希望覆寫或者混合一個例項方法,可以這樣:

// 可以傳遞一個字面量去繼承
var Alien = SuperHuman.extend({
  beam: function() {
    this.supr()
    // beam into space
  }
})

var Spazoid = new Alien('Zoopo')

if (beamIsDown) {
  // 覆寫beam方法
  Spazoid.implement({
    beam: function() {
      this.supr()
      // fallback to jets
      this.jets()
    }
  })
}

下面看一下klass原始碼解析:

klass的基本設計思路很明確,極力的模仿其它語言的繼承方式。比如:子類構造方法呼叫父類的構造方法,還可以顯式的宣告呼叫父類的方法。

這種判斷都是基於正則匹配:fnTest = /xyz/.test(function () {xyz;}) ? /\bsupr\b/ : /.*/;關鍵字"super"

如果顯示的宣告瞭要呼叫父類的方法,那麼宣告方法的時候,就包裝成一個內部呼叫父類方法且返回相同值的函式,給當前類的方法。

另一方面,構造方法,也是比較靈活的。如果顯示的宣告瞭initialize,那麼這就是構造方法。否則如果引數是個function那麼它就做為構造方法,否則就用父類的構造方法。

通過statics方式新增靜態方法,通過例項的implements和靜態方法methods新增例項方法。

通過父類的extend實現繼承。

同時,類庫為commonJS和瀏覽環境都提供了支援!

/**
  * Klass.js - copyright @dedfat
  * version 1.0
  * https://github.com/ded/klass
  * Follow our software http://twitter.com/dedfat :)
  * MIT License
  */
!function (context, f) {
  // fnTest用來驗證是否可能通過正則找出呼叫super父類方法的方法
  var fnTest = /xyz/.test(function () {xyz;}) ? /\bsupr\b/ : /.*/,
      noop = function (){},
      proto = 'prototype',
      isFn = function (o) {
        return typeof o === f;
      };
  // 基礎類
  function klass(o) {
    return extend.call(typeof o == f ? o : noop, o, 1);
  }

  // 包裝成一個借用super同名方法的函式
  function wrap(k, fn, supr) {
    return function () {
      // 快取原this.super
      var tmp = this.supr;
      // 暫把this.super改造成借用super的同名方法above
      // 供o裡顯式的宣告(fnTest.text(fn)==true)要借用super的同名方法使用
      this.supr = supr[proto][k];
      // 借用執行並儲存返回值
      var ret = fn.apply(this, arguments);
      // 恢復原this.super
      this.supr = tmp;
      // 返回返回值,保證wrap後的返回值跟原來一致
      return ret;
    };
  }
   // 如果o和super有同名方法,且o顯式宣告借用super的同名方法,就wrap成一個待執行函式供使用
   // 如果沒有顯式的宣告借用super的同名方法,或者是o獨有的方法,或者不是方法就直接用
  function process(what, o, supr) {
    for (var k in o) {
      // 如果是非繼承方法,按方法註釋規則執行,最終都放進what
      if (o.hasOwnProperty(k)) {
        what[k] = typeof o[k] == f
          && typeof supr[proto][k] == f
          && fnTest.test(o[k])
          ? wrap(k, o[k], supr) : o[k];
      }
    }
  }
  // 繼承方法的實現,fromSub是用來控制是否繼承而來,上面的klass裡面fromSub是1,表明非繼承而來,建構函式不借用super執行
  function extend(o, fromSub) {
    // noop做為媒介類實現原型繼承的解除引用
    noop[proto] = this[proto];
    
    var supr = this,
        prototype = new noop(), // 建立例項物件供原型繼承使用,解除引用
        isFunction = typeof o == f,
        _constructor = isFunction ? o : this,// 如果o是一個構造方法就用,否則由this來決定建構函式
        _methods = isFunction ? {} : o,    // 如果o是一個{...}應該用methods放到fn原型裡,如果裡面有initialize就是建構函式,如果o是函式就由上面_constructor決定o是建構函式
        fn = function () { // 因為kclass藉助了kclass,所以最終實際上返回的就是fn,fn其實就新類的建構函式
          
          //1 如果o是{...}就會被methods直接過濾並新增到fn的原型裡,如果o裡面有initialize,那麼fn的原型裡就有initialize,那麼它就是構造方法
          //2 如果o是function,methods什麼也新增不到fn的原型裡,但是_constructor會接受o當建構函式
          //3 如果o是{....},同時裡面也沒有initialize,那麼就是this當建構函式,如果在klass裡由call決定,顯然建構函式是noop,如果在非基礎類裡,建構函式就是父類的建構函式
          //  由於o不是函式不會自動呼叫父類的建構函式,只是把父類的建構函式當做當前類的建構函式----這都是由於this的指向決定的
          console.log(this);
          if (this.initialize) {
            this.initialize.apply(this, arguments);
          } else {
            // 呼叫父類構造方法
            // 如上面3,o不是函式,不會呼叫父類的構造方法
            // 基礎類無父類,不會呼叫父類構造方法
            fromSub || isFn(o) && supr.apply(this, arguments);
            // 呼叫本類構造方法
            // 參考上面2,3要麼是noop要麼是o
            console.log(_constructor==noop);
            _constructor.apply(this, arguments);
          }
        };
    // 構造原型方法的介面
    fn.methods = function (o) {
      process(prototype, o, supr);
      fn[proto] = prototype;
      return this;
    };
    // 執行實現新類原型,保證新類的constructor
    fn.methods.call(fn, _methods).prototype.constructor = fn;
    // 保證新類可以被繼承
    fn.extend = arguments.callee;
    // 新增例項方法或者靜態方法,statics:靜態方法,implement例項方法
    fn[proto].implement = fn.statics = function (o, optFn) {
      // 保證o是一個object物件,如果o是一個字串,那麼就是添一個方法的情況,如果o是一個object物件說明是批量新增的
      // 因為要從o裡面拷貝
      o = typeof o == 'string' ? (function () {
        var obj = {};
        obj[o] = optFn;
        return obj;
      }()) : o;
      // 新增例項方法或者靜態方法,statics:靜態方法,implement例項方法
      process(this, o, supr);
      return this;
    };

    return fn;
  }

  // 後臺用,nodejs
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = klass;
  } else {
    
    var old = context.klass;
    // 防衝突
    klass.noConflict = function () {
      context.klass = old;
      return this;
    };
    // 前臺瀏覽器用
    //window.kclass = kclass;
    context.klass = klass;
  }

}(this, 'function');

 三、還有一種簡單實現

實現思路很簡單,就是利用ECMAScript 5 原型式繼承Object.create方法,封裝成一個方法,如果不支援ECMAScript5的環境,就平移退化到

function F() {};  
F.prototype = superCtor.prototype;  
ctor.prototype = new F();  
ctor.prototype.constructor = ctor;

同樣的,除最後一個引數是當前類的方法宣告,其它引數均做為繼承父類,需要迴圈繼承,但當這裡處理的相對比較簡單,沒涉及到覆蓋。你可以自己動手新增。

var Class = (function() {  
      
    /**  
     * Inherits function.(node.js)  
     *   
     * @param ctor subclass's constructor.  
     * @param superctor superclass's constructor.  
     */  
    var inherits = function(ctor, superCtor) { 
        // 顯式的指定父類
        ctor.super_ = superCtor;  
          
        // ECMAScript 5  原型式繼承並解除引用
        if (Object.create) {  
            ctor.prototype = Object.create(superCtor.prototype, {  
                constructor: {  
                    value: ctor,  
                    enumerable: false,  
                    writable: true,  
                    configurable: true  
                }  
            });  
        } else {  
            // 無Object.create方法的平穩退化
            function F() {};  
            F.prototype = superCtor.prototype;  
            ctor.prototype = new F();  
            ctor.prototype.constructor = ctor;  
        }  
    };  
      
    /**  
     * Class function.  
     */  
    return function() {  
        // 最後一個引數是新類方法、屬性和建構函式宣告
        var subClazz = arguments[arguments.length - 1] || function() {};  
        // initialize是建構函式,否建構函式就是一個空函式
        var fn = subClazz.initialize == null ? function() {} : subClazz.initialize;  
        // 繼承除最一個引數以的類,多繼承,也可以用作擴充套件方法 
        for (var index = 0; index < arguments.length - 1; index++) {  
            inherits(fn, arguments[index]);  
        }  
        // 實現新類的方法
        for (var prop in subClazz) {  
              
            if (prop == "initialize") {  
                continue;  
            }  
              
            fn.prototype[prop] = subClazz[prop];  
        }  
        
        return fn;  
    }  
      
})(); 

看下面例項:

/**  
 * The definition of Cat Class.  
 */  
var Cat = Class({  
      
    /**  
     * Constructor.  
     *   
     * @param name Cat's name  
     */  
    initialize: function(name) {
        this.name = name;  
    },  
      
    /**  
     * Eat function.  
     */  
    eat: function() {  
        alert(this.name + " is eating fish.");  
    }  
});  
  
/**  
 * The definition of Black Cat Class.  
 */  
var BlackCat = Class(Cat, {  
      
    /**  
     * Constructor.  
     *   
     * @param name Cat's name.  
     * @param age Cat's age.  
     */  
    initialize: function(name, age) {  
        // call the constructor of super class.  
        BlackCat.super_.call(this, name);  
        this.age = age;  
          
    },  
      
    /**  
     * Eat function.  
     */  
    eat: function() {  
        alert(this.name + "(" + this.age + ") is eating dog.");  
    }  
});  
  
/**  
 * The definition of Black Fat Cat Class.  
 */  
var BlackFatCat = Class(BlackCat, {  
      
    /**  
     * Constructor.  
     *   
     * @param name Cat's name.  
     * @param age Cat's age.  
     * @param weight Cat's weight.  
     */  
    initialize: function(name, age, weight) {  
        // call the constructor of super class.  
        BlackFatCat.super_.call(this, name, age);  
        this.weight = weight;  
    },  
      
    /**  
     * Eat function.  
     */  
    eat: function() {  
        alert(this.name + "(" + this.age + ") is eating dog. My weight: " + this.weight);  
    }  
});  
  
  
/**  
 * The definition of Dog Class.  
 */  
var Dog = Class({});  
  
var cat = new BlackFatCat("John", 24, "100kg");  
cat.eat();  
  
// true  
alert(cat instanceof Cat);  
  
// true  
alert(cat instanceof BlackCat);  
  
// true  
alert(cat instanceof BlackFatCat);  
  
// true  
alert(cat.constructor === BlackFatCat);  
  
// false  
alert(cat instanceof Dog);  

 四、mootools類庫的Class

原始碼解析可以看這裡:http://www.cnblogs.com/hmking/archive/2011/09/30/2196504.html

看具體用法:

a、新建一個類

var Cat = new Class({
    initialize: function(name){
        this.name = name;
    }
});
var myCat = new Cat('Micia');
alert(myCat.name); // alerts 'Micia'
 
var Cow = new Class({
    initialize: function(){
        alert('moooo');
    }
});

b、繼承的實現

var Animal = new Class({
    initialize: function(age){
        this.age = age;
    }
});
var Cat = new Class({
    Extends: Animal,
    initialize: function(name, age){
        this.parent(age); // calls initalize method of Animal class
        this.name = name;
    }
});
var myCat = new Cat('Micia', 20);
alert(myCat.name); // alerts 'Micia'.
alert(myCat.age); // alerts 20.

c、擴充類的實現

var Animal = new Class({
    initialize: function(age){
        this.age = age;
    }
});
var Cat = new Class({
    Implements: Animal,
    setName: function(name){
        this.name = name
    }
});
var myAnimal = new Cat(20);
myAnimal.setName('Micia');
alert(myAnimal.name); // alerts 'Micia'.

五、悟透javascript:語法甘露

先看用法例項

a、建立類

// 建立類Person
var Person = Class(object, {
    Create : function(name, age) {
        this.name = name;
        this.age = age;
    },
    SayHello : function() {
        alert("Hello, I'm " + this.name + ", " + this.age + " years old.");
    }
});

var BillGates = New(Person, ["Bill Gates", 53]);
BillGates.SayHello();

b、繼承類

// Employee繼承Person
var Employee = Class(Person, {
    Create : function(name, age, salary) {
        Person.Create.call(this, name, age);
        //呼叫基類的建構函式
        this.salary = salary;
    },
    ShowMeTheMoney : function() {
        alert(this.name + " $" + this.salary);
    }
});

var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]);
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();

下面是原始碼分析:顯然,多了一個New方法,建立類和新建類的例項都被巧妙的封裝了。形成了一個有意義的整體!還有一點不同的地方,所有的類都基於字面量,而不是基於函式。程式碼很簡短,但其中原理卻很豐富也很巧妙,可以細細品味一番!

//建立類的函式,用於宣告類及繼承關係
function Class(aBaseClass, aClassDefine) {
    //建立類的臨時函式殼
    function class_() {
        this.Type = aBaseClass;
        //我們給每一個類約定一個Type屬性,引用其繼承的類
        for (var member in aClassDefine)
        this[member] = aClassDefine[member];
        //複製類的全部定義到當前建立的類
    };
    class_.prototype = aBaseClass;
    return new class_();
};

//建立物件的函式,用於任意類的物件建立
function New(aClass, aParams) {
    //建立物件的臨時函式殼
    function new_() {
        this.Type = aClass;
        //我們也給每一個物件約定一個Type屬性,據此可以訪問到物件所屬的類
        if (aClass.Create)
            aClass.Create.apply(this, aParams);
        //我們約定所有類的建構函式都叫Create,這和DELPHI比較相似
    };
    new_.prototype = aClass;
    return new new_();
};

 

由於寫的比較籠統,可能有很多地方沒有解析到,也可能有不準確的地方,還望指正。

 看完上面幾種解析,相資訊自己也可以寫出一個自己的封裝類庫出來,至於,怎麼實現看個人喜好了。但基本的思都是一樣的基於原型的繼承方式和迴圈拷貝新方法。

 

相關文章