日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

JavaScript撤銷恢復操作的實現方法詳解

瀏覽:368日期:2022-06-01 16:10:51
目錄
  • 前言
  • 一、初期設想
  • 二、如何收集狀態
    • 1.通信嘗試
    • 2.如何通信
  • 三、管理者與執行者
    • 1.數據驅動
    • 2.管理者
    • 3.執行者

前言

這是一個基于原生JavaScript+Three.js的系統, 我需要在里面增加撤銷恢復的功能, 這并非針對一個功能, 而是各種操作.

主要記錄思路.

一、初期設想

棧似乎很合適, 用棧存儲狀態.

最近的一次操作入棧存在于棧頂, 而撤銷操作只需要對棧頂的狀態進行操作, 這遵循棧的后進先出原則(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 }

收集狀態就不得不去滿地的找了, 操作都寫好了, 還是不要亂動.

除非單獨寫一套獨立的邏輯, 執行系統的同時也執行我自己的, 但這基本是要重寫一套系統了(

1.通信嘗試

但還是要想辦法把各處的數據都劃拉到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.

2.如何通信

如果將一個對象放在公用的模塊里, 從各個文件觸發去操作這一個對象呢…公用模塊里的數據總不至于對來自不同方向的訪問做出不同的回應吧?

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會在許多地方引入, 它最好不要太大太復雜.

1.數據驅動

傳參的方案十分不合理, 最好能用別的方法向回撤函數描述要執行怎樣的回撤操作.

在入棧的時候直接于數據中描述該份數據如何進行回撤似乎也行, 但是以字符串描述出來該如何執行?

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也抽出去? 似乎沒必要.

2.管理者

參考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這個操作比較簡單也沒有多種情況, 就沒必要分配執行者了…

3.執行者

執行者名單需要為一個對象, 這樣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撤銷恢復操作的實現方法詳解的文章就介紹到這了,更多相關JS撤銷恢復操作內容請搜索以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持!

標簽: JavaScript
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产精品日本一区二区三区在线| 日本伊人久久| 蜜臀久久99精品久久久久久9| 国产+成+人+亚洲欧洲在线| 国产剧情一区二区在线观看| 最新国产精品久久久| 女主播福利一区| 久久精品影视| 久久精品99国产精品日本| 蜜臀91精品一区二区三区| 亚洲免费网址| 蜜桃一区二区三区在线| av不卡免费看| 亚洲在线久久| 蜜臀精品久久久久久蜜臀 | 亚洲一区二区三区四区电影| 在线亚洲欧美| 美女黄网久久| 日韩国产在线不卡视频| 日韩一区中文| 91精品国产一区二区在线观看 | 国产欧美日本| 国产一区二区三区天码| | 国产一区91| 日韩精品成人| 国产一区二区精品福利地址| 久久黄色影院| 亚洲深深色噜噜狠狠爱网站 | 国产精品羞羞答答在线观看| 久久精品日韩欧美| 亚洲精品网址| 国产精品亚洲综合久久| 欧美国产美女| 亚洲日本久久| 亚洲天堂资源| 91精品在线免费视频| 久久激情中文| 69堂精品视频在线播放| 另类中文字幕国产精品| 欧美在线资源| 国产成人精品免费视| 蜜臀精品一区二区三区在线观看| 精品国产午夜肉伦伦影院| 综合国产精品| 成人久久一区| 欧美国产精品| 日韩一区精品| 日韩一区欧美二区| 四虎884aa成人精品最新| 国产欧美一区二区精品久久久| 久久亚洲国产| 国产精品草草| 国产成人精品福利| 日韩va亚洲va欧美va久久| 视频一区国产视频| 久久精品影视| 国产精品大片| 亚洲一区二区动漫| 精品国产乱码久久久久久樱花| 日本精品另类| 香蕉久久夜色精品国产| 麻豆mv在线观看| 国产欧美日韩影院| 日本中文字幕视频一区| 群体交乱之放荡娇妻一区二区| 国产精品porn| 日本在线一区二区三区| 狠狠爱成人网| 欧美特黄视频| 综合日韩av| 久久精品国产网站| 国产日韩欧美三级| 日本免费在线视频不卡一不卡二| 午夜欧美在线| 在线综合视频| 国产精品婷婷| 一区二区亚洲视频| 鲁大师影院一区二区三区| 国产在线欧美| 久久蜜桃av| 色婷婷色综合| a国产在线视频| 日韩精品一区二区三区中文 | 久久精品99久久久| 日本一区中文字幕| 久久99视频| 国产一区二区三区四区| 久久香蕉网站| 麻豆国产欧美日韩综合精品二区| 欧美aaaaaa午夜精品| 精品一区二区三区四区五区| 欧美激情麻豆| 欧美激情麻豆| 亚洲高清成人| 视频一区二区中文字幕| 99久久婷婷| 三级欧美在线一区| 青青草精品视频| а√天堂8资源中文在线| 激情婷婷久久| 蜜桃免费网站一区二区三区| 日韩av成人高清| 里番精品3d一二三区| 久久裸体视频| 91亚洲精品在看在线观看高清| 红杏一区二区三区| 亚洲在线久久| 日韩在线观看不卡| 午夜久久黄色| 国产欧美日韩一级| 国产精品久久久久av电视剧| 伊人久久成人| 麻豆国产欧美一区二区三区| 国精品一区二区| 日本中文字幕一区二区视频| 久久精品九色| 亚洲欧美网站| 韩国久久久久久| 91综合久久爱com| 欧美/亚洲一区| 欧美成a人片免费观看久久五月天| 亚洲少妇一区| 综合日韩av| 国产激情精品一区二区三区| 91久久黄色| 日本特黄久久久高潮| 国产亚洲一级| 麻豆一区二区三| 亚洲欧洲日韩精品在线| 国产欧美一区二区三区精品酒店| 视频一区二区三区中文字幕| 成人欧美一区二区三区的电影| 国产免费久久| 日韩亚洲精品在线观看| 国产亚洲网站| 在线国产一区二区| 五月激情久久| 久久久久国产精品一区三寸| а√天堂中文在线资源8| 麻豆一区在线| 日韩高清不卡一区二区| 在线一区二区三区视频| 久久午夜精品一区二区| 黄色成人91| 女同性一区二区三区人了人一| 国产精品久久久久久久久妇女| 麻豆视频久久| 精品中文在线| 国产成人久久精品麻豆二区 | 亚洲欧洲一区二区天堂久久| 激情综合激情| 国产成人久久精品麻豆二区| 加勒比视频一区| 91精品一区二区三区综合在线爱 | 激情综合五月| 国产美女高潮在线观看| 日韩1区2区| 不卡在线一区二区| 伊人精品一区| 亚洲精品伊人| 精品久久精品| 日韩av一级| 亚洲免费观看高清完整版在线观| 青草综合视频| 激情不卡一区二区三区视频在线| 国产欧美日韩在线一区二区| 精品视频一二| 精品一区免费| 亚洲日产国产精品| 久久精品人人| 免费久久99精品国产自在现线| 爽好久久久欧美精品| 国内一区二区三区| 美女精品一区| 国产伦理一区| 午夜欧美在线| 日本欧美一区二区| 欧洲av不卡| 在线精品亚洲| 丁香婷婷久久| 欧美手机在线| 欧美黄页在线免费观看| 免费成人网www| 精品国产亚洲一区二区三区在线| 亚洲免费一区二区| 国内精品美女在线观看| 蜜桃久久久久久久| 久久久久99| 欧美激情福利| 日韩一区二区三区免费视频| jiujiure精品视频播放| 精品99在线| 日韩精品乱码av一区二区| 久久精品国产大片免费观看| 国产精品17p| 婷婷综合五月| 久久三级视频| 日韩精品免费一区二区夜夜嗨| 亚洲国产专区|