JavaScript中的物件模型(object model)並不廣為人知。我曾寫過一篇關於他們的部落格。之所以不被人所熟知,原因之一就是JavaScript是這些被人廣泛使用的語言中唯一一個透過原型(prototype)來實現繼承的。但是,我認為另一個原因就是這種物件模型非常複雜,難於解釋。為什麼它這麼複雜又令人困惑呢?那是因為JavaScript試圖去隱藏它傳統的物件導向的特性──最終導致了它的雙重人格(譯者註:作者意思是JavaScript既有面向過程的特徵,又有物件導向的特徵)。
我認為正是由於JavaScript物件模型的難以理解和使用,才出現了一些像CoffeeScript,Dart和TypeScript這些透過編譯可以產生JS程式碼的語言。
JavaScript的前輩們和那些頑固派相信JavaScript有更好的物件模型,並且為其將被大家所遺忘感到惋惜。即使JavaScript的專家Nicholas Zakas也歡迎在ECMAScript 6中加入的新的class語法——只不過是對原型風格(prototypal style)的語法做了一些修飾。換句話說,傳統的OOP贏了。
一個大膽的設想
但是,讓我們以玩笑的方式做一個設想:我們假想穿越到過去,那時候傳統的面向對象的程序設計還沒有像現在這樣被大家廣泛的接受,相反的,基於原型的繼承模型得到了大家的廣泛接受。這樣的話會發生什麼事?我們最終又會得到什麼樣的設計模式呢?
我們再設想:假設JavaScript沒有建構子或沒有new關鍵字會怎麼樣?事情又會變成什麼樣的呢?讓我們推到以前的重來。 :)
首先,第一件事情,在JavaScript中,我們可以使用物件字面量的來建立一個新物件。如下圖所示:
var felix = {
var felix = {
var felix = { name : 'Felix',
greet: function(){
console.log('Hello, I am ' this.name '.');
}
};
接下來,假設我們想要將greet函數一般化,將其提取出來,放到一個一般的位置,這樣一來,我們就可以創建多個物件來共享同一個greet方法。怎麼實現呢?
我們有好幾種選擇,先以mixin開始吧。
複製程式碼
程式碼如下:
var Dude = {
greet: function(){
console.log('Hello, I am ' this.name '.')
}
};
};
var felix = { name: 'Felix' };
augment(felix, Dude);//將Dude中的屬性複製一份到felix中,即混入(mixin)
複製程式碼
程式碼如下:
function augment(obj, properties){
for (var key in properties){
obj[key] = properties[key]; }
}
複製程式碼
程式碼如下:
var Did = {
: function(){
console.log('Hello, I am ' this.name '.'); } } var felix = clone(Dude);//克隆Dude物件felix.name = 'Felix';
這兩種方法之間的唯一不同就是新增屬性的順序。如果你想覆寫克隆的物件中的某些方法,你可以考慮使用這種手法。
var felix = clone(Dude); .name = 'Felix';
felix.greet = function(){
console.log('Yo dawg!');
};//覆蓋greet方法
如果想要呼叫父類別的方法也很簡單-使用apply函數即可,如下圖
felix.greet = function(){
Dude.greet.apply(this);
this.greetingCount ;
}
this.greetingCount ;
}
這比原型風格的程式碼好很多,因為你不必去使用建構函式的.prototype屬性-我們不會使用任何建構函式。
程式碼如下:
程式碼如下:
function🎜 (obj){
var retval = {};//建立空物件
augment(retval, obj);//複製屬性
return retval; }
複製程式碼
程式碼如下:
複製程式碼
程式碼如下:
var garfield = inherit( Dude);//garfield繼承自Dude
Dude.walk = function(){//給Dude添加新的方法walk
console.log('Step, step');
};
garfield.walk(); // prints "Step, step"
複製程式碼
程式碼如下:
function inherit(proit(proit(proto)> if (Object.create){
// 使用ES5中的Object.create方法
return Object.create(proto);
}else if({}.__proto__){
//使用非標準屬性__proto__
var ret = {};
ret.__proto__ = proto;
return ret;
}else{
//如果兩種都不支持,使用建構函數繼承
var f = function(){};
f.prototype = proto;
return new f();
}
}
複製程式碼
程式碼如下:
}, greet: function(){ console.log('Hello, I am ' this.name '.'); this.greetingCount ; } } 然後,你可以這樣來初始化物件 複製程式碼 程式碼如下:
var felix = clone(Dude);
felix.name = 'Felix';
felix.initialize();或也可以
var felix = { name: 'Felix' } ;
felix.name = 'Felix';
augment(felix, Dude);
felix.initialize();還可以
var felix = inherit(Dude);
felix.name = 'Felix';
felix.initialize();結語
我表示透過上面定義的三個函數-augment,clone和inherit,你可以對JavaScript中的物件做任何你想做的事,而不必使用建構函式和new關鍵字。我認為這三個函數所體現的語意更簡單且更接近JavaScript底層的物件系統。 (完)^_^