說明:
在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