成人在线亚洲_国产日韩视频一区二区三区_久久久国产精品_99国内精品久久久久久久

您的位置:首頁技術(shù)文章
文章詳情頁

JavaScript撤銷恢復(fù)操作的實(shí)現(xiàn)方法詳解

瀏覽:332日期:2022-06-01 16:10:51
目錄
  • 前言
  • 一、初期設(shè)想
  • 二、如何收集狀態(tài)
    • 1.通信嘗試
    • 2.如何通信
  • 三、管理者與執(zhí)行者
    • 1.數(shù)據(jù)驅(qū)動(dòng)
    • 2.管理者
    • 3.執(zhí)行者

前言

這是一個(gè)基于原生JavaScript+Three.js的系統(tǒng), 我需要在里面增加撤銷恢復(fù)的功能, 這并非針對(duì)一個(gè)功能, 而是各種操作.

主要記錄思路.

一、初期設(shè)想

棧似乎很合適, 用棧存儲(chǔ)狀態(tài).

最近的一次操作入棧存在于棧頂, 而撤銷操作只需要對(duì)棧頂?shù)臓顟B(tài)進(jìn)行操作, 這遵循棧的后進(jìn)先出原則(LIFO).

然后再設(shè)置一系列方法去操作棧, 增加一點(diǎn)安全性.

首先是各種功能應(yīng)該在什么地方發(fā)起出入棧操作, 這里有一大堆js文件.

其次對(duì)于不同的功能需要收集什么參數(shù)來組織狀態(tài)入棧.

不同的功能出棧應(yīng)該分別進(jìn)行什么操作, 回撤出??隙ㄒ{(diào)這堆文件里的方法來把老狀態(tài)填回去.

二、如何收集狀態(tài)

先寫個(gè)demo,我建了一個(gè)數(shù)組棧放在class Manager, 設(shè)置了入棧方法push, 出棧方法pop以及查看棧頂?shù)?code>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 }

收集狀態(tài)就不得不去滿地的找了, 操作都寫好了, 還是不要亂動(dòng).

除非單獨(dú)寫一套獨(dú)立的邏輯, 執(zhí)行系統(tǒng)的同時(shí)也執(zhí)行我自己的, 但這基本是要重寫一套系統(tǒng)了(

1.通信嘗試

但還是要想辦法把各處的數(shù)據(jù)都劃拉到Manager里.

呃, 老實(shí)說我并沒有什么原生開發(fā)的經(jīng)驗(yàn), 我在多個(gè)文件里引入了Manager類并且期望著這些文件可以基于Manager建立聯(lián)絡(luò)實(shí)現(xiàn)數(shù)據(jù)共享, 比如在a.js和b.js內(nèi):

只是舉個(gè)例子, 不要用一個(gè)字母去命名文件.

// 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);

然而這樣做并不能實(shí)現(xiàn)數(shù)據(jù)共享, 每一個(gè)剛剛實(shí)例化出來的對(duì)象都是嶄新的.

const manager = new Manager();

只是使用原始干凈的class Manger實(shí)例化了一個(gè)僅存在于這個(gè)模塊里的對(duì)象manager.

2.如何通信

如果將一個(gè)對(duì)象放在公用的模塊里, 從各個(gè)文件觸發(fā)去操作這一個(gè)對(duì)象呢…公用模塊里的數(shù)據(jù)總不至于對(duì)來自不同方向的訪問做出不同的回應(yīng)吧?

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 }

之后分別在各個(gè)js文件引入manager, 共同操作該模塊內(nèi)的同一個(gè)manager, 可以構(gòu)成聯(lián)系, 從不同位置向manager同步數(shù)據(jù).

manager幾乎像服務(wù)器里的數(shù)據(jù)庫, 接受存儲(chǔ)從各處發(fā)送的數(shù)據(jù).

三、管理者與執(zhí)行者

現(xiàn)在入棧方案基本確定了, 一個(gè)入棧方法push就能通用, 那出棧怎么辦呢.

不同的操作必須由不同的出棧方法執(zhí)行.

最初設(shè)想是寫一個(gè)大函數(shù)存在class manager里, 只要發(fā)起回撤就調(diào)這個(gè)函數(shù), 至于具體執(zhí)行什么, 根據(jù)參數(shù)去確定.

但是這方法不太好.

首先, 我會(huì)在用戶觸發(fā)ctrl + z鍵盤事件時(shí)發(fā)起回撤調(diào)用回撤函數(shù), 但是我只在這一處調(diào)用, 如何判定給回撤函數(shù)的參數(shù)該傳什么呢? 如果要傳參, 我怎么在ctrl + z事件監(jiān)聽的地方獲取到該回撤什么操作以傳送正確的參數(shù)呢?

另外, 如果這樣做, 我需要在manager.js這一個(gè)文件里拿到所有回撤操作需要的方法和它們的參數(shù), 這個(gè)項(xiàng)目中的大部分文件都以一個(gè)巨大的類起手, 構(gòu)造函數(shù)需要傳參, 導(dǎo)出的還是這個(gè)類, 我如果直接在manager里引入這些文件去new它們, 先不說構(gòu)造函數(shù)傳參的問題, 生成的對(duì)象是嶄新的, 會(huì)因?yàn)橐恍┓椒]有調(diào)用導(dǎo)致對(duì)象里的數(shù)據(jù)不存在或者錯(cuò)誤, 而我去使用這些數(shù)據(jù)自然也導(dǎo)致錯(cuò)誤.

我最好能拿到回撤那一刻的數(shù)據(jù), 那是新鮮的數(shù)據(jù), 是有價(jià)值的.

另外manager會(huì)在許多地方引入, 它最好不要太大太復(fù)雜.

1.數(shù)據(jù)驅(qū)動(dòng)

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

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

switch嗎, 那需要在回撤函數(shù)內(nèi)寫全部處理方案, 哪怕處理方案抽離也需要根據(jù)switch調(diào)取函數(shù), 就像這樣:

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;
      // ...
    }
  }
}

將來萬一要加別的功能的回撤, 一個(gè)函數(shù)百十行就不太好看了, 還是在類里面的函數(shù).

那…把switch也抽出去? 似乎沒必要.

2.管理者

參考steam, 嗯, 就是那個(gè)游戲平臺(tái))

steam可以看作游戲的啟動(dòng)器吧, 拋開人工操作, 如果需要啟動(dòng)游戲,那么先啟動(dòng)steam, steam再去啟動(dòng)游戲, steam可以看作一個(gè)管理者.

管理者只需要去決定, 并且調(diào)用分派事項(xiàng)給正確的執(zhí)行者就好, 管理者自己不執(zhí)行.

參考你老板.

然后Manager可以作為這樣一個(gè)角色, 它只負(fù)責(zé)維護(hù)狀態(tài)和分配任務(wù):

import { Exec } from "./exec.js";
import { deepCopy } from "../util.js";
const executors = new Exec(); // 執(zhí)行者名單
class Manager {
  constructor() {
    this.editor = null;
    this.doCount = 0;
    this.doActions = [];
    this.undoCount = 0;
    this.undoActions = [];
    this.justUndo = false;
    this.justRedo = false;
  }
  do(action) { // 增加狀態(tài)
    if (this.justUndo || this.justRedo) { // undo/redo后, world不應(yīng)立即入棧
      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() { // 回撤事項(xiàng)分配
    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() { // 恢復(fù)事項(xiàng)分配
     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() { // 重置狀態(tài)
    this.doCount = 0;
    this.doActions = [];
    this.undoCount = 0;
    this.undoActions = []
  }
  peek(which) { // 檢視狀態(tài)
    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, 我的狀態(tài)收集是在一次請求前, 這個(gè)請求函數(shù)固定在每次世界變化之后觸發(fā), 將當(dāng)前的世界狀態(tài)上傳. 所以為了避免回撤或恢復(fù)世界操作調(diào)用請求函數(shù)將回撤或恢復(fù)的世界再次重復(fù)加入棧內(nèi)而設(shè)置.

undo或者redo這兩種事情發(fā)生后, 執(zhí)行者manager通過原生數(shù)組方法獲取到本次事項(xiàng)的狀態(tài)對(duì)象(出棧), 借助getFunction(看作它的秘書吧)訪問執(zhí)行者名單, 幫自己選取該事項(xiàng)合適的執(zhí)行者, 并調(diào)用該執(zhí)行者執(zhí)行任務(wù)(參考undo, redo函數(shù)體).

執(zhí)行者名單背后是一個(gè)函數(shù)庫一樣的結(jié)構(gòu), 類似各個(gè)部門.

這樣只需要無腦undo()就好, manager會(huì)根據(jù)本次的狀態(tài)對(duì)象分配執(zhí)行者處理.

do這個(gè)操作比較簡單也沒有多種情況, 就沒必要分配執(zhí)行者了…

3.執(zhí)行者

執(zhí)行者名單需要為一個(gè)對(duì)象, 這樣getFunction()秘書才能夠?yàn)?code>manager選出合適的執(zhí)行者, 執(zhí)行者名單應(yīng)為如下結(jié)構(gòu):

// 執(zhí)行者有擅長回撤(undo)和恢復(fù)(redo)的兩種
{
  planA: planAFun (data, params) {
    // ...
  },
  planAUndo: planAUndoFun (data, params) {
    // ...
  },
  planB: planBFun () {
    // ...
  },
  planBUndo: planBUndoFun (data, params) {
    // ...
  }
  ...
}

也好, 那就直接把所有執(zhí)行者抽離為一個(gè)類, 實(shí)例化該類后自然能形成這種數(shù)據(jù)結(jié)構(gòu):

class Exec { // executor
  saveWorldRedo (data, params) {
    // ...
  }
  saveWorldUndo (data, params) {
    // ...
  }
  initialWorldUndo (data, params) {
    // ...
  }
}
export { Exec };

實(shí)例化后:

{
  saveWorldRedo: function (data, params) {
    // ...
  },
  saveWorldUndo: function (data, params) {
    // ...
  },
  initialWorldUndo: function (data, params) {
    // ...
  }
}

正是需要的結(jié)構(gòu).

getFunction可以由解析狀態(tài)對(duì)象進(jìn)而決定枚舉executor對(duì)象中的哪個(gè)執(zhí)行者出來調(diào)用:

const executor = getFunction (name) {
  return executors[name];
}

到此這篇關(guān)于JavaScript撤銷恢復(fù)操作的實(shí)現(xiàn)方法詳解的文章就介紹到這了,更多相關(guān)JS撤銷恢復(fù)操作內(nèi)容請搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!

標(biāo)簽: JavaScript
成人在线亚洲_国产日韩视频一区二区三区_久久久国产精品_99国内精品久久久久久久
不卡一二三区首页| 亚洲国产免费看| 欧美先锋影音| 亚洲资源在线观看| 欧美精品一二三| 亚洲国产电影| 国产精品一区在线观看乱码| 日本一区二区三区久久久久久久久不 | 亚洲免费影院| 99国产精品视频免费观看| 亚洲国产精品久久不卡毛片| 欧美tk丨vk视频| 在线精品视频一区二区三四 | 日韩欧美卡一卡二| 一区二区av| 成人av集中营| 蜜臀av性久久久久蜜臀aⅴ| 中文字幕一区二区三区精华液| 日韩午夜电影av| 午夜宅男久久久| 欧美日韩亚洲一区| 国产精品99久久久久久有的能看 | 欧美色图片你懂的| 亚洲深夜福利| 欧美日韩国产高清| 成人av在线资源网| 国产精品白丝jk白祙喷水网站| 午夜视黄欧洲亚洲| 亚洲免费在线看| 久久精品欧美日韩| 日韩欧美激情在线| 欧美福利电影网| 欧美三级韩国三级日本一级| 久久综合九色| 99国内精品| 国产欧美大片| aⅴ色国产欧美| 99在线观看免费视频精品观看| 国产一区视频在线观看免费| 97超碰欧美中文字幕| 97se亚洲国产综合在线| 高清日韩电视剧大全免费| 国产最新精品免费| 国产激情偷乱视频一区二区三区| 午夜私人影院久久久久| 亚洲裸体xxx| 《视频一区视频二区| 最新国产の精品合集bt伙计| 日韩美女精品在线| 一区二区三区在线视频免费| 亚洲一区二区av在线| 亚洲精品日韩综合观看成人91| 国产三级一区二区| 亚洲日本免费电影| 亚洲h在线观看| 男女男精品视频网| 国内精品自线一区二区三区视频| 狠狠色综合色综合网络| 狠狠色狠狠色综合系列| 亚洲一区av在线| 亚洲激情图片小说视频| 一区二区三区中文免费| 日一区二区三区| 久久se精品一区二区| 国产v综合v亚洲欧| a4yy欧美一区二区三区| 午夜电影亚洲| 一区二区三区三区在线| 香蕉久久夜色精品国产| 91麻豆精品国产91久久久 | 国产精品少妇自拍| 亚洲欧美日韩精品在线| 国产一区999| 中文成人综合网| 色婷婷久久久综合中文字幕| 成人一级片在线观看| 亚洲人成精品久久久久| 欧美日韩午夜在线| 欧美 日韩 国产在线| 亚洲午夜精品17c| 日韩亚洲国产中文字幕欧美| 99国产精品久久久久老师| 国产资源在线一区| 国产欧美一区二区精品婷婷 | 精品综合免费视频观看| 欧美成人r级一区二区三区| 99视频一区| 成人免费毛片嘿嘿连载视频| 欧美激情91| 国产日韩欧美三区| 精品少妇一区二区三区日产乱码 | 国产成人无遮挡在线视频| 欧美.www| 欧美群妇大交群中文字幕| 中文字幕一区二区三区蜜月| 麻豆精品久久精品色综合| 欧美精品一区二区三区久久久竹菊| 国产精品一区二区a| 日韩久久久精品| 一区二区在线观看不卡| 岛国av在线一区| 久久一区二区三区av| 国产精品久久久久国产精品日日| 日韩国产成人精品| 黄色工厂这里只有精品| 欧美三级资源在线| 亚洲精品亚洲人成人网| 懂色av噜噜一区二区三区av| 老妇喷水一区二区三区| 国产精品久久久久久久久免费桃花| 国产制服丝袜一区| 久久都是精品| 国产精品视频免费看| 国产一区二区三区美女| 久久福利影视| 亚洲情趣在线观看| 成人深夜福利app| 欧美羞羞免费网站| 亚洲一区二区不卡免费| 免费在线一区二区| 国产精品久久久久久久久果冻传媒| 丁香亚洲综合激情啪啪综合| 91福利精品视频| 亚洲123区在线观看| 最新成人av网站| 欧美r级在线观看| 国产精品亚洲专一区二区三区| 色菇凉天天综合网| 一级日本不卡的影视| 欧美特黄视频| 国产日韩欧美综合在线| 懂色av一区二区夜夜嗨| 在线成人免费视频| 黑人精品欧美一区二区蜜桃| 色哟哟国产精品| 天天色天天爱天天射综合| 久久精品成人| 青娱乐精品视频| 在线观看国产精品网站| 亚洲成人一区二区在线观看| 一区二区冒白浆视频| 夜夜嗨av一区二区三区| 免费亚洲视频| 免费视频最近日韩| 欧美影院一区二区| 国产真实乱偷精品视频免| 欧美日韩免费观看一区二区三区 | 欧美亚洲专区| 亚洲欧美国产三级| 国产精品一区二区在线观看| 亚洲国产欧美在线人成| 91福利在线导航| 久久精品av麻豆的观看方式| 色狠狠综合天天综合综合| 精品一区二区三区日韩| 欧美一区中文字幕| 99视频在线精品| 国产精品成人在线观看| 榴莲视频成人在线观看| 狠狠色丁香久久婷婷综合_中| 精品少妇一区二区三区视频免付费| www.欧美色图| 亚洲女同ⅹxx女同tv| 久久婷婷影院| 不卡视频免费播放| 亚洲欧美中日韩| 日本乱人伦一区| 成人av在线电影| 亚洲欧美电影院| 色综合久久88色综合天天6| 国产美女av一区二区三区| 亚洲精品一区二区三区影院 | 国产亚洲欧美日韩在线一区| 韩国一区二区三区美女美女秀| 日韩精品亚洲专区| 26uuu另类欧美亚洲曰本| 国产亚洲毛片| 国产在线精品一区二区| 中文字幕av资源一区| 亚久久调教视频| 国产精品一区三区| 亚洲男同性视频| 欧美日产国产精品| gogo大胆日本视频一区| 一区二区三区国产| 日韩精品一区二区在线观看| 亚洲东热激情| 国产ts人妖一区二区| 中文字幕五月欧美| 6080国产精品一区二区| 亚洲黄色av| 成人黄色小视频在线观看| 亚洲综合色区另类av| 欧美电影免费观看高清完整版在线 | 亚洲国产精品一区制服丝袜| 免费在线观看视频一区| 国产精品久久免费看| 欧美日韩不卡在线| 在线视频观看日韩| 99视频热这里只有精品免费|