開發時遇到這樣的需求,希望通過一個類似factory的機制來建立對象,這個機制接收兩個參數,一個是對象所屬的類,另一個是參數數組,例如:
function getObject(cls, args){<br /> //....<br />}
這裡cls是一個類(即javascript函數),args是一個參數數組,例如[arg1, arg2, ....],希望這個函數返回的對象等同於下面代碼的效果:
new cls(arg1, arg2...);
容易想到,我們應該用apply方法,因為javascript中構建對象的過程就是執行建構函式的過程。而建構函式與普通函數性質完全一樣,只是它會被new關鍵字自動調用而已。apply方法接收2個對象,第一個是函數啟動並執行內容物件,即this對象。在這裡對象尚未構建,因此我們使用一個Null 物件:
function getObject(cls, args){<br /> var obj = {};<br /> cls.apply({}, args);<br /> return obj;<br />}
為驗證可行性,我們建立1個類:
function employee(name, age){<br /> this.name = name;<br /> this.age = age;<br />}
下面用getObject建立一個employee對象並確認其屬性:
var emp = getObject(employee, ['Jack', 26]);<br />alert(emp.name + ': ' + emp.age);
可以看到,正如我們所希望的,對象被正確建立。
但事情到此並沒有完。問題出在apply函數的第一個參數上:我們用了一個Null 物件。實際上起始物件不一定為空白。當empoyee的prototype也被定義時,就不為空白了,例如,我們通過prototype為employee增加一個方法:
empoyee.prototype.show = function(){<br /> alert(this.name + ': ' + emp.age);<br />}
再建立對象,並調用show方法:
var emp = getObject(employee, ['Jack', 26]);<br />emp.show();
會出錯,說show方法不存在,其原因就是我們只用到了emp的建構函式,卻沒用到其prototype。
實際上,根據javascript規範,new一個對象時,首先是以函數的prototype對象作為原型,在這個基礎上去調用建立新對象。因此我們需要修改getObject方法,讓apply以prototype為基礎。
但同時,不能直接以employee.prototype作為apply的第一個參數,因為那是直接修改了empoyee的原型。我們需要建立一份prototype的拷貝。如果用過dojo.delegate方法,我們應該知道複製對象最高效的做法,用其思路,來重寫getObject方法:
function getObject(cls, args){<br /> function _cls(){};<br /> _cls.prototype = cls.prototype;<br /> var obj = new _cls();<br /> cls.apply(obj, args);<br /> return obj;<br />}<br />//再執行如下代碼:<br />var emp = getObject(employee, ['Jack', 26]);<br />emp.show();
可以看到,show方法也被正確構建。這說明得到了正確的getObject方法。完整的例子可參見:
http://jsfiddle.net/V94Z2/
雖然這個需求的實現看似簡單,實際上它要求熟知javascript new關鍵字的工作原理,知其原理,我們就能成功的把對象的構建分成2個部分,一部分是prototype的複製,另一部分是建構函式的執行。用自己的代碼去實現這兩部分,就能實現我們需要的功能了。