Prototype源碼淺析——Class部分(一)之類


說明:

在javascript中,由於缺乏傳統面向對象語言的繼承機制,類與繼承是一個比較復雜的概念。因此本段解析中,不就javascript中的原型、類、繼承、封裝進行深入探討。需要深入了解的可以去參考大牛的文章,另推薦幾本書《javascript高級程序設計》、《javascript語言精髓與編程實踐》和《javascript設計模式》,多讀幾遍就會對javascript的原型有深入的了解。

所以本文只就Prototype遠源碼涉及到的部分進行解析。

 

正文:

到Class這一步,需要說一下前面沒有說到的單體模式,通過前面的幾個部分的觀察,會發現整個Prototype部分,大部分對象的方法擴展采用的都是類似下面的形式:

    var obj = (function(){
var variable ;
function method(){}
return {
method:method
}
})();

這就是單體模式。

不過這么做有什么好處呢?

單體簡單的理解就是一個對象。比如:

    var obj = {
name : 'xesam',
say : function(){
console.log(this.name);
}
};
obj.say();

此時,obj有個作用就是可以提供一個命名空間,並且組織了一部分變量和方法。

不夠這樣的一個去缺點就是name完全暴露在外面,可以隨意修改,如果name對我們來說是私有的,那么這么樣就無法滿足要求了。所以鑒於JS中奇特的作用域限制,我們自然得依靠函數來幫忙,於是修改如下:

    var obj = function(){
var name = 'xesam';
function say(){
console.log(name);
}
};
obj.say();

此時,name變成一個局部變量,外界就無法訪問了,不過此時obj.say()也會報錯,因為obj已經不是一個對象(這里的對象是指普通對象,而非函數這種對象)了,因此我們再讓函數自執行,返回我們需要的對象:

    var obj = (function(){
var name = 'xesam';
function say(){
console.log(name);
}
return {
say : say
}
})();
obj.say();

於是就得到了類似Prototype中的形式,這么做的好處大概也就出來了,變量和方法的組織,數據的封裝和隱藏。兩外,由於匿名函數只執行(實例化)了一次,所以也不會帶來內存的問題。如果願意,也可以這么樣來模擬JS中的Math對象。

現在回到Prototype的Class部分,我們來一步步實現Prototype中的Class部分。

對於最常見的類的聲明,就是創建一個構造函數,然后擴展其原型。比如:

    function Person(name){
this.name = name;
}
Person.prototype.say = function(){
console.log('hello ' + this.name);
}
var xesam = new Person('xesam');
xesam.say();

 

上面定義構造函數和添加方法分開了,現在我們把他們打包到一起。運用剛才說故偶的單體模式,我們先定義一個對象,用作命名空間:

var Class = {}

現在需要提供的是一個初始化變量和添加方法的功能。

寫之前我們先得規定一下最終形式的調用方法,我們還是尊重Prototype的形式,並且以上面的Person類為例,聲明Person類的形式為:

    var Person = Class.create({
initialize:function(name){
this.name = name;
},
say : function(){
console.log('hello ' + this.name);
}
});
var xesam =new Person('xesam');
xesam.say();

如上所示,Class.create的參數是一個對象,其中initialize包含的是初始化部分(相當於普通面向對象的構造函數),其他部分(say)則是需要添加的方法

【說明:initialize是必須的,其他則可選】

在實現之前,我們先准備一個工具函數$A,作用就是獲得一個對象的數組形式(轉化為一個數組):

  function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}

這個方法挺簡單的,無需多說。

 

下面我們可以獲得一個最簡單的Class.create實現:

        var Class = {};
Class.create = function(source){
return function(){
for(var i in source){
this[i] = function(){
source[i].apply(this,$A(arguments));
}
//如果你還記得Function部分的內容,這里可以寫成this[i] = source[i].bind();
}
source.initialize.apply(this,arguments);
}
}

var Person = Class.create({
initialize:function(name){
this.name = name;
},
say : function(){
console.log('hello ' + this.name);
}
});
var xesam =new Person('xesam');
xesam.say();

上面create返回的是一個匿名函數,我們還可以采用另一種方式,先聲明一個函數,處理這個函數之后再返回:

        Class.create = function(source){
var kclass = function(){
this.initialize.apply(this,arguments);
};
for(var i in source){
kclass.prototype[i] = source[i];
}
return kclass;
};

這里先前的實現其實差不多,而且和最初的Person比較吻合。

 

分析上面的實現,你會發現定義完成之后,除了去改動初始定義,就沒辦法擴展了。因此我們在打包了之后再將具體實現分開,將Class分為初始化和添加方法兩個部分,並使用剛才說過的單體模式來組織:

        var Class = (function(){
function create(source){
function kclass(){
this.initialize.apply(this,arguments);
};
addMethods.call(kclass,source);//注意這里的調用
return kclass;
}
function addMethods(source){
for(var property in source){
this.prototype[property] = source[property];
}
}
return {
create : create,
addMethods : addMethods
}
})();

這么處理過之后,addMethods方法分離出來了,但是現在還是在Class對象上,並不會添加到我們新創建的Person類上面,所以我們將addMethods添加到實現中的kclass上面。

kclass.addMethods = addMethods;//(代碼4-1)

注意,這里有個關於擴展性的問題。如果我們的Class並不是只有addMethods一個方法,而且有addMethods1,addMethods2,addMethods3···方法,那么我們總不能按照這個順序一並寫下去吧,所以我們可以用一個對象(比如叫Methods)來把這些方法都組織起來。所以代碼4-1的形式變成:

kclass.addMethods = Methods.addMethods;

繼續分析,create的參數可能是多個需要添加的方法,因此不能用一個形參source來限定死了,采用內置的arguments對象來替代。所以Class的基本骨架就出來了:

        var Class = (function(){
function create(){
var properties = $A(arguments);
function kclass(){
this.initialize.apply(this,arguments);//實例化的時候,這里自動調用了initialize方法
};
kclass.addMethods = Class.Methods.addMethods;
//在Prototype中由於有一個Object方法,所以這里調用的是Object.extend(Class.Methods)方法
for(var i = 0; i < properties.length; i++){
kclass.addMethods(properties[i]);
}
if (!kclass.prototype.initialize){//這里是一個小檢測,避免因找不到initialize而報錯
kclass.prototype.initialize = function(){};
}
return kclass;
}
function addMethods(source){
for(var property in source){
this.prototype[property] = source[property];
}
}
return {
create : create,
Methods : { //注意這里的改動
addMethods : addMethods
}
}
})();

 

到此,我們有了一個Class對象(可以創建類和添加方法)和一個Person類(有name屬性和say方法)。

 

轉載請注明來自小西山子【http://www.cnblogs.com/xesam/
本文地址:http://www.cnblogs.com/xesam/archive/2011/12/27/2303121.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM