今天要介紹的是 Prototype 模式,這是 GoF 提出的模式之一。
在軟體開發中,有些物件具有高相似度,或是使用的方法、功能類似,開發者需要一種方式來避免建立重複的方法,提高創建物件的彈性。
如何快速且靈活的建立一個物件,配置已有物件的屬性和方法,而不是從頭開始建立一個全新物件?
new
關鍵字來建立新物件時,在建構過程中可能有複雜計算,而帶來額外效能負擔,尤其是若需要創建大量類似物件時,頻繁的 new
建構物件會增加運算成本Prototype 模式可使用 JavaScript 原生提供的 prototype
優勢,因 JavaScript 中的 prototype(原型)是原生就有的物件,我們可用原型來讓同類型物件間共享屬性和方法。
可用兩種方式來撰寫 JavaScript 的原型繼承,第一種是 ES6 推出的 class
建構子,小提醒,class
只是建構函式的語法糖,JavaScript 中沒有 class
的概念,在 JavaScript 寫 class
本質上還是在用傳統的建構函式(function constructor)來建立實例,還不清楚 class
和建構函式的可參考 PJ 大大的筆記。
假設在前端應用中,我們想設計一個圖形編輯器,會有正方形、圓形等圖形,他們會有類似的屬性和行為,我們可用 class 來定義圖形的原型。
// 定義 Shape 建構子來讓不同圖形共用顏色屬性和繪製方法(這裡先假設繪製行為是相同的)
class Shape {
constructor(color) {
this.color = color;
}
draw() {
console.log(`Drawing a ${this.color} shape`);
}
}
接下來我們可依據這個形狀的基本屬性和方法,延伸定義矩形和圓形建構子。
// 矩形 class
class Rectangle extends Shape {
constructor(width, height, color) {
super(color); // 用 super 繼承 Shape 的屬性
this.width = width;
this.height = height;
}
draw() {
super.draw(); // 執行 Shape 的 draw 方法
console.log(`This is a rectangle with width ${this.width} and height ${this.height}`); // 接著再執行新定義的邏輯,這樣可同時執行共用方法內的邏輯,又可擴展矩形自己想執行的行為
}
getArea() { // 定義矩形自己的方法
return this.width * this.height;
}
}
// 圓形 class
class Circle extends Shape {
constructor(radius, color) {
super(color); // 用 super 繼承 Shape 的屬性
this.radius = radius;
}
draw() {
super.draw(); // 執行 Shape 的 draw 方法
console.log(`This is a circle with radius ${this.radius}`); // 接著再執行新定義的邏輯
}
}
都定義完後,可用 new
關鍵字來建立實例,並呼叫方法。
const myRectangle = new Rectangle(10, 5, 'blue');
myRectangle.draw(); // 會印出兩段文字,分別是 'Drawing a blue shape',以及 'This is a rectangle with width 10 and height 5'
console.log(`myRectangleArea: ${myRectangle.getArea()}`); // 印出 'myRectangleArea: 50'
const myCircle = new Circle(7, 'red');
myCircle.draw(); // 會印出兩段文字,分別是 'Drawing a red shape',以及 'This is a circle with radius 7'
可看出我們並沒有在 Rectangle
和 Circle
建構子中明確定義 draw
方法要印出這是在繪製哪個顏色的圖形,只有說要繼承 Shape
的方法,因 JavaScript 原型繼承的關係,當我們呼叫 myRectangle.draw()
時就會向上層追到 Shape
原型定義的方法,這就是 prototype 的用意,可讓物件使用共通的方法,而不需要重複定義一樣的邏輯,共用方法也因而能節省記憶體的使用。
Object.create
Object.create
可讓我們將其他物件作為原型使用,並依此創建新物件。使用方式是 Object.create(prototype, propertiesObject)
,第一個參數 prototype
是指定要創建物件的原型物件,第二個參數 propertiesObject
是選填,可指定要創建的物件的初始化屬性。
舉例來說,如果我們有一個 myCar
物件如下:
const myCar = {
name: 'FooBar',
drive(){
console.log('Weee. I am driving!')
}
};
我們可以用 Object.create
來建立新物件,並指定要以 myCar
物件作為原型:
const newCar = Object.create(myCar);
console.log(newCar.name); // 因為 newCar 會繼承 myCar 屬性,所以 name 會印出 FooBar
// 如果要更改 newCar 的 name,再指派一個值給 newCar.name 即可
newCar.name = 'Midnight';
console.log(newCar.name); // Midnight
以 Prototype 作為解決方案優點如下:
以 Prototype 作為解決方案缺點如下:
JavaScript 的原型鏈設計可讓我們使用許多已定義好的共用方法,例如 forEach
是陣列原型的方法,所以我們在建立陣列時,不需要為新陣列重新定義 forEach
方法,就可以透過原型鏈的方式往上呼叫,可看出平常實務應用很常使用到原型的概念~