cover

你在做一個 UI 編輯器,使用者拖拉出一個按鈕元件,設定了寬度、高度、顏色、圓角、陰影。然後他想要「再做一個差不多的,只是顏色不同」。

你要讓他從頭設定一次嗎?還是直接複製一個、改個顏色就好?

先講結論

Prototype Pattern 的核心就是 clone()。把一個已經設定好的物件複製一份,再微調需要變的部分。比起從頭建立,省去了重複的初始化成本。

classDiagram
    class Prototype {
        <<interface>>
        +clone()* Prototype
    }
    class UIComponent {
        -width : Number
        -height : Number
        -color : String
        +clone() UIComponent
        +render() String
    }
    Prototype <|.. UIComponent
    UIComponent ..> UIComponent : clone() 產生副本

實戰:UI 元件複製

Prototype 模式:複製機快速產出多個變體

class UIComponent {
    constructor(width, height, color) {
        this.width = width;
        this.height = height;
        this.color = color;
        // 假設這裡有很多昂貴的初始化...
    }
 
    clone() {
        return Object.assign(
            Object.create(Object.getPrototypeOf(this)),
            this
        );
    }
 
    render() {
        return `<div style="width:${this.width}px;height:${this.height}px;background:${this.color}"></div>`;
    }
}
 
// 建立原型
const buttonPrototype = new UIComponent(100, 40, 'blue');
 
// 複製 + 微調
const confirmButton = buttonPrototype.clone();
confirmButton.color = 'green';
 
const cancelButton = buttonPrototype.clone();
cancelButton.color = 'red';
 
console.log(confirmButton.render()); // green 按鈕
console.log(cancelButton.render());  // red 按鈕

三個按鈕,但只有第一個跑了完整的 constructor。後面兩個都是複製 + 改顏色。

JavaScript 本身就是 Prototype-based

有趣的是,JavaScript 的繼承機制本身就是 prototype-based。每個物件都有一個 __proto__,指向它的原型。你每次用 Object.create() 或 class 的 extends,底層都在用 prototype chain。

所以在 JavaScript 裡,Prototype Pattern 不像在 Java/C++ 那麼「特別」——它就是語言本身的一部分。

小心淺拷貝

Object.assign 做的是淺拷貝。如果物件裡有巢狀物件(array、object),clone 出來的副本會跟原型共用同一個 reference。改了一個、另一個也跟著變。

需要深拷貝的話,用 structuredClone()(現代瀏覽器和 Node 18+ 支援)或 JSON.parse(JSON.stringify(obj))(但不支援 function 和 Date)。


Prototype Pattern 就像影印機——原稿印好了,後面要幾份就印幾份,再用立可白改幾個字就好。只是影印太多次品質會越來越差


延伸閱讀