寫在前面
在開始之前我們先關注一下這個模式的名字:Constructor(構造器)模式,GoF 的 23 個設計模式中沒有這一個,名字字面上最相似應該是 Builder(生成器)模式。但 Builder 的實際含義與書中介紹的並不一樣:
生成器模式(Builder):用來封裝組合結構(樹形結構)的構造過程,與迭代器模式類似,都隱藏了組合結構的內部實現,只提供一組用於創建組合結構的接口
(引自 [黯羽輕揚:設計模式總結](/articles/設計模式總結(《head-first 設計模式》學習總結)/))
所以,Constructor 應該是指一種為了彌補 JavaScript 沒有提供基於 class 的繼承而提出的設計模式,那就沒有什麼神秘的了
一、Constructor(構造器)模式
從書中的實例來看,構造器模式是用來實現屬性共享的。所謂「構造器模式」,其實只是一個基本常識:把私有屬性定義在構造函數中,把公有屬性定義在原型對象上,代碼如下:
function Fun(arg) {
this.arg = arg;
// 私有屬性
var attr = 1;
function fun() {
// ...
}
}
// 公有屬性(在原型對象上定義函數屬性是為了函數在各個實例間能夠共享,減少內存佔用)
Fun.prototype.fun = function() {
// ...
}
console.log(new Fun(1).fun === new Fun(2).fun); // true
這樣做確實有一定優點(減少內存佔用),但存在很大問題(比如原型引用屬性在實例間共享),作者沒有繼續展開,僅僅給一句話的常識貼上「設計模式」的標籤就匆匆結束了。
關於 JavaScript 的繼承,筆者之前做過詳細的解釋,此處不再贅述,請查看 黯羽輕揚:重新理解 JS 的 6 種繼承方式
二、最佳繼承方式
重新梳理一遍 JavaScript 的繼承方式,直接看代碼,代碼自己會說話:
/* 父類 */
function Super(arg) {
this.arg = arg;
// 私有屬性
var attr = 1;
function fun() {
// ...
}
}
// 公有屬性(在原型對象上定義函數屬性是為了函數在各個實例間能夠共享,減少內存消耗)
Super.prototype.fun = function() {
// ...
}
/* 子類 */
function Sub(arg, newArg) {
// 繼承父類實例屬性
Super.call(this, arg);
// 初始化子類實例屬性
this.newArg = newArg;
// ...
}
// 缺點:無法向父類構造函數傳參
// Sub.prototype = new Super();
function F() {} // 空函數,借來生孩子(beget)
F.prototype = Super.prototype;
var proto = new F(); // 得到「純潔」的孩子
Sub.prototype = proto; // 繼承父類原型屬性
// test
var sub = new Sub(1, 2);
// 輸出父類屬性
console.log(sub.arg); // 1
console.log(new Sub(3, 4).fun === new Sub(5, 6).fun); // true
上面的實現還存在一點瑕疵,缺少了proto.constructor = Sub;這一行,導致sub.constructor是指向 Super 的,實際上我們希望它指向 Sub,添上缺少的那行就好了
三、一點廢話
3 個月前的面試中被 JS 的繼承問住了,之後重新理解了 6 中繼承方式,到現在 3 個月過去了,印象很深刻(直接寫出了上面的代碼,雖然存在一點瑕疵。。)。由此可見,花時間去理解一個東西是很有用的,否則現在肯定就被「Constructor(構造器)模式」的大帽子唬住了
參考資料:
-
《JavaScript 設計模式》
暫無評論,快來發表你的看法吧