Javascript有多種建立物件的方式,新手和老手都可能感到不知所措,不確定應該使用哪種方式。文字將介紹常見的物件常見模式和最佳的實踐。
物件字面量:Object Literals建立物件最簡單的方式就是物件字面量。Javascript總是吹噓能夠“無中生有”地建立物件——不需要類、不需要模板、不需要原型——just poof!,一個有方法和資料的物件就出現了。
var o = { x: 42, y: 3.14, f: function() {}, g: function() {}};
缺點
如果需要在其他地方建立相同型別的物件,將要複製貼上物件的方法、資料和初始化的程式碼,導致大量重複程式碼。需要一種能夠批次建立同類型物件的方法,而不僅僅是一個物件。
工廠模式:Factory Functions這是建立相同結構、介面和實現的物件最簡單的方式。而不是直接建立物件字面量,而是將物件字面量作為函式的返回值。這樣,如果需要多次或多個地方建立相同型別的物件時只需要呼叫一個函式:
function thing() { return { x: 42, y: 3.14, f: function() {}, g: function() {} };}var o = thing();
缺點
這種Javascript物件建立方法可能會導致記憶體膨脹,因為每一個物件都包含了工廠函式的獨立副本。理想情況下,我們希望每個物件只共享其功能的一個副本。
建構函式模式可以建立特定型別的物件, 類似於Array, Date等原生JS的物件.其實現方法如下:
function Student(name,age){ this.name=name; this.age=age; this.myName=function(){ alert(this.name); };}var student1_ = new Student('aaa',15);var student2_ = new Student('bbb',18);
缺點
每次例項化一個物件都會把構造器裡的所有方法重新建立一次,多次建立會造成記憶體開銷增加的問題。
原型鏈:Prototype ChainsJavascript提供了一種內建的在物件之間共享資料的機制,稱為原型鏈。當訪問物件的屬性時,它可以透過委託給其他物件來滿足該請求。可以利用這一點來修改工廠函式,使它建立的每個物件只包含自己特有的資料,而對其他屬性的請求則全部委託給原型鏈上共有的一個物件:
var thingPrototype = { f: function() {}, g: function() {}};function thing() { var o = Object.create(thingPrototype); o.x = 42; o.y = 3.14; return o;}var o = thing();
事實上,這是一種常見的模式,語言已經內建了對它的支援。不需要建立自己的共享物件(原型物件)。相反,會自動為每個函式建立一個原型物件,可以將共享資料放在那裡:
thing.prototype.f = function() {};thing.prototype.g = function() {};function thing() { var o = Object.create(thing.prototype); o.x = 42; o.y = 3.14; return o;}var o = thing();
缺點
會導致重複。上述thing函式的第一行和最後一行在每一個“委託原型的工廠函式”中都會重複一次,幾乎沒有區別。
ES5類:ES5 Classes可以把那些重複的程式碼抽出來,放進一個自定義函數里來隔離它們。這個函式會建立一個物件,並與其他某個任意函式(引數函式)的原型建立委託(繼承)關係,然後把新建立的物件作為引數,呼叫這個函式(引數函式),最後返回這個新的物件。
function create(fn) { var o = Object.create(fn.prototype); fn.call(o); return o;}// ...Thing.prototype.f = function() {};Thing.prototype.g = function() {};function Thing() { this.x = 42; this.y = 3.14;}var o = create(Thing);
事實上,這也是一種常見的模式,Javascript有一些內建的支援。create定義的函式實際上是new關鍵字的基本版本,可以直接替換create為new(建構函式+原型鏈):
Thing.prototype.f = function() {};Thing.prototype.g = function() {};function Thing() { this.x = 42; this.y = 3.14;}var o = new Thing();
在ES5中,它們是物件建立函式,它將共享資料委託給原型物件,並依賴new關鍵字來處理重複邏輯。
缺點冗長和醜陋,實現繼承更加冗長和醜陋。
ES6類:ES6 Classes在ES6的類中,執行相同的操作提供了更清晰的語法:
class Thing { constructor() { this.x = 42; this.y = 3.14; } f() {} g() {}}const o = new Thing();
比較
多年以來,Javascript開發者們與原型鏈的關係總是若即若離,糾纏不清。而今天最有可能遇到的兩種建立物件的方式,一種是強烈依賴原型鏈的class語法,另一種則是完全不依賴原型鏈的工廠函式語法。這兩種風格在效能和功能上有所不同——儘管差別不太大。
效能今天Javascript引擎已經過如此大量最佳化,以至於很難透過Javascript程式碼來推斷怎樣會比較快。關鍵在於測量方法。然而有時甚至測量都會讓我們失望。通常情況下,每六週釋出一次更新的Javascript引擎,有時效能會發生重大變化,我們之前進行的任何測量以及我們根據這些測量做出的任何決定都會立即出現。因此,經驗法則是支援最官方和最廣泛使用的語法,假設它將受到最嚴格的審查,並且在大多數時候是最高效的。目前來看class語法最符合這一點,class語法大約比返回字面量的工廠模式快3倍。
特點隨著ES6的釋出,類與工廠模式之間曾經存在的幾點差異消失了。現在,工廠模式和類都能夠強制實現真正的私有資料:
工廠模式透過閉包實現類透過weak maps實現兩者都能實現多重繼承——工廠模式可以將其他屬性混入自己的物件,類也可以將其他屬性混入自己的原型,或者透過類工廠,透過代理也能實現。工廠函式和類也都可以在需要的時候返回任意物件,語法也都很簡單。
結論考慮到所有事情,對Javascript物件建立的偏好是使用類語法。它是標準的,它簡單而乾淨,速度快,並且它提供了曾經只有工廠模式才能提供的所有功能
設計模式