Prototype Pattern是一種建立型模式,在GoF Book中它的意圖被描述成用原型執行個體指定建立對象的種類,並通過拷貝這些原型建立新的對象。
Prototype Pattern本身實際上非常簡單,任何一個提供了clone()方法的對象都可以成為原型對象,所有通過它複製的對象都屬於一類對象。在靜態語言中,這一模式被用於運行時指定物件類型,相比原廠模式,這一模式無需建立與類層次平行的工廠類結構,實現上要方便得多。
clone的三種js實現
在js中,clone方法的實現並不困難,對js的6種基本類型來說 string boolean undefined null number皆可直接用=賦值,唯一麻煩的是object。
對object 我們可以用clone其所有成員的方式複製 作為方法的函數可以這樣定義:
[複製]
Code:
function clone()
{
var ret=new Object();
for(var p in this)
{
ret[p]=this[p];
}
}
但我們顯然面對著一個問題:this[p]也可能是一個object 所以很可能我們需要用遞迴來實現deepClone
[複製]
Code:
function deepClone()
{
var ret=new Object();
for(var p in this)
{
ret[p]=deepClone.call(this[p]);
}
}
對js來說 實現clone還有另外一種方式,在javascript中,構造器的prototype屬性指明了某一類的原型,當執行個體化時,這一原型被作為對象的原型使用。特別地,這個prototype對象也可能是從某一原型構造出來的,這形成了一個類似繼承的結構,所以javascript的面向原型特性又被稱作原型繼承(儘管我很不贊同這種做法,還是要提一下)。
回到我們前面的Prototype Pattern,javascript天生的引用型原型繼承為我們提供了另外一種clone的實現方式:
[複製]
Code:
function prototypeClone()
{
var tmp=function(){};
tmp.prototype=this;
return new tmp;
}
這樣clone出來的對象唯讀地共用一個原型的屬性,它的最大優勢是速度非常快,當我們希望快速複製大型物件時,可以使用這種方式,但是它會造成訪問速度降低,而且它即時反映父節點的變化。
內建對象的clone
但是,到這裡為止,我們還沒有考慮內建對象,內建對象不能用普通方法clone 我們要考慮的內建對象有這麼幾個:
Function Array String Boolean Number Date
RegExp Error和Math沒有需要clone的情境 所以不在我們的考慮之中。
對Function來說,完全產生一個副本是不可能的,因為我們無法保證構造的函數跟原來的函數在同一範圍,但是不包含範圍的實現是很容易的:
eval(this);
或者使用Function構造
return Function(new String("return ")+this)();
Function本身是個Object 因此必須加上Object的clone 實現functionPrototypeClone需要一點小花招
[複製]
Code:
function functionClone()
{
var ret=Function(new String("return ")+this)();
for(var p in this)
{
ret[p]=this[p];
}
}
function functionDeepClone()
{
var ret=Function(new String("return ")+this)()
for(var p in this)
{
ret[p]=deepClone.call(this[p]);
}
}
function functionPrototypeClone()
{
var tmp=Function.prototype;
Function.prototype=this;
var ret=(new Function(new String("return ")+this))();
Function.prototype=tmp;
return ret;
}
Array只要保證length正確就可以了
[複製]
Code:
function arrayClone()
{
var ret=new Array();
for(var p in this)
{
ret[p]=this[p];
}
}
function arrayDeepClone()
{
var ret=new Array();
for(var p in this)
{
ret[p]=deepClone.call(this[p]);
}
}
function arrayPrototypeClone()
{
var tmp=Array.prototype;
Array.prototype=this;
var ret=new Array();
Array.prototype=tmp;
return ret;
}
Date對象提供了getTime 所以可以很容易實現
[複製]
Code:
function arrayClone()
{
var ret=new Date();
ret.setTime(this.getTime());
for(var p in this)
{
ret[p]=this[p];
}
}
function arrayDeepClone()
{
var ret=new Date();
ret.setTime(this.getTime());
for(var p in this)
{
ret[p]=deepClone.call(this[p]);
}
}
function arrayPrototypeClone()
{
var tmp=Date.prototype;
Date.prototype=this;
var ret=new Date();
ret.setTime(this.getTime());
Date.prototype=tmp;
return ret;
}
String Boolean Number都是唯讀對象,所以只要=就可以了。
前面討論了三種Clone的實現方法,它們各自具有適合的語義環境,比如對一個數組來說 若是把它理解為一個集合Collection 則應該使用淺clone(假如集合A是B的子集,則應保證A.clone()亦是B的子集),若是把它理解為一個向量Vector,則應使用深clone(保證對向量A的分量操作不應影響向量A.clone()的分量)。prototypeClone的一個最常見的應用情境是深度優先搜尋演算法演算法,為了擴充解空間樹,我們通常需要快速的構造一個副本,如果使用clone或者deepClone 這將非常慢,而深度優先搜尋的特點是在位元組點被銷毀之前,父節點不會變化,所以prototypeClone是非常合適的。
附:Prototype-oriented Programming和Prototype Pattern
面向原型的語言思想跟原型模式是完全一致的:從同一原型clone出來的對象就是一類對象。Prototype-oriented的語言對這種模式提供了語言層級的支援,即所有"類"的定義都是通過指定該類的一個原型來實現的(Class-Based Programming是通過類結構聲明來描述一類對象,meta-class則是通過構造一個"類對象"來描述一類對象)。每次執行個體話就clone一次原型,然而這種方式會造成資訊的冗餘:所有對象都持有原型對象的一個clone的副本,而且一旦某一對象被構造,修改原型不會對它造成任何影響,這對於希望在程式中統一改變某一類對象的人來說很不方便。於是,一種變通的方法產生了:引用型原型對象,與之相對,原來的原型對象使用方法被稱為 複製型原型對象。引用型原型對象不再clone原型,而是儲存一個指向原型的指標,當訪問屬性時,首先檢查自己的屬性,當查到不存在時,則通過指標向原型索取相應屬性。而引用型原型就是javascript的面向原型特性的實現方式。
PS.本文徵求過本系列原作者意見了 不是盜版滴 呵呵