您好,登錄后才能下訂單哦!
今天小編給大家分享一下JavaScript撤銷恢復的方法是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
棧似乎很合適, 用棧存儲狀態.
最近的一次操作入棧存在于棧頂, 而撤銷操作只需要對棧頂的狀態進行操作, 這遵循棧的后進先出原則(LIFO).
然后再設置一系列方法去操作棧, 增加一點安全性.
首先是各種功能應該在什么地方發起出入棧操作, 這里有一大堆js文件.
其次對于不同的功能需要收集什么參數來組織狀態入棧.
不同的功能出棧應該分別進行什么操作, 回撤出棧肯定要調這堆文件里的方法來把老狀態填回去.
先寫個demo,我建了一個數組棧放在class Manager
, 設置了入棧方法push
, 出棧方法pop
以及查看棧頂的peek
方法.
class Manager { constructor() { this.stats= []; } push(action) { this.stats.push(action); } pop() { const nowAction = this.doActions.pop(); } peek(which) { return this.doActions[this.doCount]; } } export { Manager }
收集狀態就不得不去滿地的找了, 操作都寫好了, 還是不要亂動.
除非單獨寫一套獨立的邏輯, 執行系統的同時也執行我自己的, 但這基本是要重寫一套系統了
但還是要想辦法把各處的數據都劃拉到Manager
里.
呃, 老實說我并沒有什么原生開發的經驗, 我在多個文件里引入了Manager
類并且期望著這些文件可以基于Manager
建立聯絡實現數據共享, 比如在a.js和b.js內:
只是舉個例子, 不要用一個字母去命名文件.
// a.js import { manager } from "./backup/manager.js"; const manager = new Manager(); const action = { name: 'saveWorldList', params: { target: '108', value: [ world: { psr: {}, annotation: {} } ] } } for (let i = 0; i < 3; i++) { manager.push(action); }
// b.js import { manager } from "./backup/manager.js"; const manager = new Manager(); const undoAction = manager.pop(); console.log(undoAction);
然而這樣做并不能實現數據共享, 每一個剛剛實例化出來的對象都是嶄新的.
const manager = new Manager();
只是使用原始干凈的class Manger
實例化了一個僅存在于這個模塊里的對象manager
.
如果將一個對象放在公用的模塊里, 從各個文件觸發去操作這一個對象呢…公用模塊里的數據總不至于對來自不同方向的訪問做出不同的回應吧?
class Manager { constructor() { this.stats= []; } push(action) { this.stats.push(action); } pop() { const nowAction = this.doActions.pop(); } peek(which) { return this.doActions[this.doCount]; } } const manager = new Manager(); export { manager }
之后分別在各個js文件引入manager
, 共同操作該模塊內的同一個manager
, 可以構成聯系, 從不同位置向manager
同步數據.
manager
幾乎像服務器里的數據庫, 接受存儲從各處發送的數據.
現在入棧方案基本確定了, 一個入棧方法push
就能通用, 那出棧怎么辦呢.
不同的操作必須由不同的出棧方法執行.
最初設想是寫一個大函數存在class manager里
, 只要發起回撤就調這個函數, 至于具體執行什么, 根據參數去確定.
但是這方法不太好.
首先, 我會在用戶觸發ctrl + z
鍵盤事件時發起回撤調用回撤函數, 但是我只在這一處調用, 如何判定給回撤函數的參數該傳什么呢? 如果要傳參, 我怎么在ctrl + z
事件監聽的地方獲取到該回撤什么操作以傳送正確的參數呢?
另外, 如果這樣做, 我需要在manager.js這一個文件里拿到所有回撤操作需要的方法和它們的參數, 這個項目中的大部分文件都以一個巨大的類起手, 構造函數需要傳參, 導出的還是這個類, 我如果直接在manager
里引入這些文件去new
它們, 先不說構造函數傳參的問題, 生成的對象是嶄新的, 會因為一些方法沒有調用導致對象里的數據不存在或者錯誤, 而我去使用這些數據自然也導致錯誤.
我最好能拿到回撤那一刻的數據, 那是新鮮的數據, 是有價值的.
另外manager
會在許多地方引入, 它最好不要太大太復雜.
傳參的方案十分不合理, 最好能用別的方法向回撤函數描述要執行怎樣的回撤操作.
在入棧的時候直接于數據中描述該份數據如何進行回撤似乎也行, 但是以字符串描述出來該如何執行?
用switch
嗎, 那需要在回撤函數內寫全部處理方案, 哪怕處理方案抽離也需要根據switch
調取函數, 就像這樣:
class Manager { constructor () { this.stats = []; } pop() { const action = this.stats.pop(); switch (action) { planA: this.planAFun(action.params); break; planB: this.planBFun(action.params); break; // ... } } }
將來萬一要加別的功能的回撤, 一個函數百十行就不太好看了, 還是在類里面的函數.
那…把switch
也抽出去? 似乎沒必要.
參考steam
, 嗯, 就是那個游戲平臺)
steam
可以看作游戲的啟動器吧, 拋開人工操作, 如果需要啟動游戲,那么先啟動steam, steam再去啟動游戲, steam可以看作一個管理者.
管理者只需要去決定, 并且調用分派事項給正確的執行者就好, 管理者自己不執行.
參考你老板.
然后Manager
可以作為這樣一個角色, 它只負責維護狀態和分配任務:
import { Exec } from './exec.js'; import { deepCopy } from "../util.js"; const executors = new Exec(); // 執行者名單 class Manager { constructor() { this.editor = null; this.doCount = 0; this.doActions = []; this.undoCount = 0; this.undoActions = []; this.justUndo = false; this.justRedo = false; } do(action) { // 增加狀態 if (this.justUndo || this.justRedo) { // undo/redo后, world不應立即入棧 this.justUndo === true && (this.justUndo = false); this.justRedo === true && (this.justRedo = false); return; } this.previousWorld = action.params.value; this.doActions.push(action); this.doCount++ console.log("Do: under control: ", this.doActions); } undo() { // 回撤事項分配 if (this.doActions.length === 1) { console.log(`Cannot undo: doSatck length: ${this.doActions.length}.`); return; } const nowAction = this.doActions.pop(); this.doCount--; this.undoActions.push(nowAction); this.undoCount++; const previousAction = this.peek('do'); const executor = this.getFunction(`${previousAction.name}Undo`); executor(this.editor, previousAction.params) this.justUndo = true; console.log(`Undo: Stack now: `, this.doActions); } redo() { // 恢復事項分配 if (this.undoActions.length === 0) { console.log(`Connot redo: redoStack length: ${this.undoActions.length}.`); return; } const nowAction = this.undoActions.pop(); this.undoCount--; this.doActions.push(nowAction); this.doCount++; const previousAction = nowAction; const executor = this.getFunction(`${previousAction.name}Redo`); executor(this.editor, previousAction.params); this.justRedo = true; console.log(`Redo: Stack now: `, this.doActions); } getFunction(name) { return executors[name]; } reset() { // 重置狀態 this.doCount = 0; this.doActions = []; this.undoCount = 0; this.undoActions = [] } peek(which) { // 檢視狀態 if (which === 'do') { return this.doActions[this.doCount]; } else if (which === 'undo') { return this.undoAction[this.undoCount]; } } initEditor(editor) { this.data = editor; } } const manager = new Manager(); export { manager }
justUndo
/justRedo
, 我的狀態收集是在一次請求前, 這個請求函數固定在每次世界變化之后觸發, 將當前的世界狀態上傳. 所以為了避免回撤或恢復世界操作調用請求函數將回撤或恢復的世界再次重復加入棧內而設置.
undo
或者redo
這兩種事情發生后, 執行者manager
通過原生數組方法獲取到本次事項的狀態對象(出棧), 借助getFunction
(看作它的秘書吧)訪問執行者名單, 幫自己選取該事項合適的執行者, 并調用該執行者執行任務(參考undo
, redo
函數體).
執行者名單背后是一個函數庫一樣的結構, 類似各個部門.
這樣只需要無腦undo()
就好, manager
會根據本次的狀態對象分配執行者處理.
do
這個操作比較簡單也沒有多種情況, 就沒必要分配執行者了…
執行者名單需要為一個對象, 這樣getFunction()
秘書才能夠為manager
選出合適的執行者, 執行者名單應為如下結構:
// 執行者有擅長回撤(undo)和恢復(redo)的兩種 { planA: planAFun (data, params) { // ... }, planAUndo: planAUndoFun (data, params) { // ... }, planB: planBFun () { // ... }, planBUndo: planBUndoFun (data, params) { // ... } ... }
也好, 那就直接把所有執行者抽離為一個類, 實例化該類后自然能形成這種數據結構:
class Exec { // executor saveWorldRedo (data, params) { // ... } saveWorldUndo (data, params) { // ... } initialWorldUndo (data, params) { // ... } } export { Exec };
實例化后:
{ saveWorldRedo: function (data, params) { // ... }, saveWorldUndo: function (data, params) { // ... }, initialWorldUndo: function (data, params) { // ... } }
正是需要的結構.
getFunction
可以由解析狀態對象進而決定枚舉executor
對象中的哪個執行者出來調用:
const executor = getFunction (name) { return executors[name]; }
以上就是“JavaScript撤銷恢復的方法是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。