今天不開發新功能,今天來拆解別人的扣~
來說說 prototype 要怎麼改寫!在那之前,先來認識我們的例子 fabric-history 是怎麼寫的。
alimozdemir/fabric-history: Fabric.js history plugin (github.com)
官方文件給的只有這兩個 api
canvas.undo();
canvas.redo();
但其實裡面定義好的自定義 prototype api 的都可以拿出來用,甚至可以修改裡面邏輯 or 新增 api
fabric-history/src/index.js at master · alimozdemir/fabric-history (github.com)
覆寫 initialize 和 dispose 函數:
initialize
函數中調用 _historyInit()
來初始化歷史記錄相關的變數和事件監聽器。dispose
函數中調用 _historyDispose()
來移除事件監聽器。管理歷史記錄:
_historyNext()
方法獲取當前畫布狀態的 JSON 表示。_historyEvents()
方法定義了需要監聽的事件,包括 'object:added'
、'object:removed'
、'object:modified'
和 'object:skewing'
。_historySaveAction()
方法在發生這些事件時,將當前畫布狀態推入 historyUndo
堆疊。復原和重做操作:
undo()
方法從 historyUndo
堆疊中彈出最新的狀態,並將其推入 historyRedo
堆疊,然後重新渲染畫布。redo()
方法從 historyRedo
堆疊中彈出最新的狀態,並將其推入 historyUndo
堆疊,然後重新渲染畫布。其他輔助方法:
clearHistory()
方法清空 historyUndo
和 historyRedo
堆疊。onHistory()
和 offHistory()
方法用於控制是否記錄歷史操作。canUndo()
和 canRedo()
方法用於檢查是否有可撤銷或重做的操作。initHistory()
方法用於初始化撤銷/重做堆疊,並註冊 'object:modified'
事件來更新歷史記錄。undo()
和 redo()
方法分別實現了撤銷和重做操作。我們先不要看文件裡那些比較複雜的東西,我們先試著用最簡單的三個 function 來達成動作紀錄就好:
如果這功能的簡易版用一般 js 的方式寫:
// 紀錄復原或重做的 array
const undoStack = [];
const redoStack = [];
// 註冊自定義事件(記錄歷史)
const saveHistory = (event) => {
undoStack.push(canvas.toJSON());
redoStack.length = 0; // 清空重做堆疊
}
// 我們讓記錄歷史這件事,在新增、刪除、修改物件時觸發
canvas.on('object:added', saveHistory);
canvas.on('object:removed', saveHistory);
canvas.on('object:modified', saveHistory);
// 按下復原時
const undo = () => {
if (undoStack.length > 0) {
redoStack.push(canvas.toJSON());
canvas.loadFromJSON(undoStack.pop(), () => {
canvas.renderAll();
});
}
};
// 按下重做時
const redo = () => {
if (redoStack.length > 0) {
undoStack.push(canvas.toJSON());
canvas.loadFromJSON(redoStack.pop(), () => {
canvas.renderAll();
});
}
};
但歷史紀錄其實是一個反覆且執行頻率高的行為,我們可以考慮將之改寫進 prototype。
使用者可以輕鬆地撤銷和重做在畫布上進行的各種操作,並可以自定義需要記錄的事件。這對於需要頻繁修改和編輯的應用程序非常有用,且會讓呼叫此 function 執行更加容易(在有使用 fabricjs 的全域都可以呼叫)
這個轉換其實寫起來不會跟上面function的寫法差很多,只是架構換了一下,一起來看看吧
fabric.Canvas.prototype.initHistory = function() {
this.undoStack = [];
this.redoStack = [];
// 加上監聽器
this.on('object:added', (e) => this._saveHistory(e));
this.on('object:removed', (e) => this._saveHistory(e));
this.on('object:modified', (e) => this._saveHistory(e));
};
比較大的不同是,我們 把 undoStack
和 redoStack
直接定義在 canvas
物件的原型上,而不是作為外部變數。這樣可以確保每個 canvas
實例都有自己的 undoStack
和 redoStack
。
而且在開始其他動作之前我們會初始化這個canvas的歷史狀態。
// 註冊自定義事件(記錄歷史)
fabric.Canvas.prototype._saveHistory = function() {
this.undoStack.push(this.toJSON());
this.redoStack.length = 0; // 清空重做堆疊
}
// 按下復原時
fabric.Canvas.prototype.undo = function() {
if (this.undoStack.length > 0) {
this.redoStack.push(this.toJSON());
this.loadFromJSON(this.undoStack.pop(), () => {
this.renderAll();
});
}
};
// 按下重做時
fabric.Canvas.prototype.redo = function() {
if (this.redoStack.length > 0) {
this.undoStack.push(this.toJSON());
this.loadFromJSON(this.redoStack.pop(), () => {
this.renderAll();
});
}
};
這邊其實沒有變化太大,很籠統地說,只是將 canvas 全都換成了 this 了。
當你使用普通的function來實現功能時,這些function通常是獨立的,並且不會與特定的對象實例關聯。這種方法適合於簡單的功能或工具函數。
但當你使用 prototype
來實現功能時,這些方法會被添加到對象的原型上,並且可以被所有該對象的實例共享。這種方法適合於需要在多個實例之間共享行為的情況。像歷史紀錄這全面都會被共用的東西就很適合。
作用範圍:
canvas
對象。fabric.Canvas
的原型上,所有 fabric.Canvas
的實例都可以直接調用這些方法。代碼重用:
canvas
對象,適合於簡單的工具函數。設計模式:
選擇哪種方法取決於你的應用場景和設計需求。如果你需要在多個 fabric.Canvas
實例之間共享行為,使用 prototype
會更合適。如果你只是需要一些簡單的工具函數,使用普通的 function
會更簡潔。