如何制作 undo/redo 函数

How to make a undo/redo function

我想在我的脚本中添加一个 undo/redo 函数。我环顾四周,看到了一些建议,其中大部分建议使用 command pattern.

函数必须在一页上工作 - 重新加载页面后函数必须能够redo/undo最后的事情。

我不知道命令模式是如何工作的,我想创建一个对象来存储函数的名称、旧值和新值 - 但我不确定这是否有效这样做与否。

也许有人可以给我一个小例子 undo/redo 函数的代码应该是什么样子。

有两种常见的实现方式 undo/redo:

  • Memento Pattern,您可以在其中捕获整个 当前状态。它易于实现,但内存效率低下,因为您需要存储整个状态的相似副本。
  • Command Pattern,您在其中捕获影响状态的 commands/actions逆作用)。更难实现,因为对于应用程序中的每个可撤消操作,您必须明确编码它的反向操作,但它的内存效率要高得多,因为您只存储影响状态的操作。

纪念品模式

在应用操作之前,您拍摄当前状态的快照并将其保存到数组中。该快照是 Memento.

如果用户想要撤消,您只需 pop 最后的纪念品并应用它。程序 return 恢复到应用最后一个操作之前的状态。

这个模式是内存密集型的;每个纪念品都比较大,因为它捕获了整个当前状态。

但它也是最容易实现的,因为您不需要在命令模式(见下文)中显式编码所有情况及其逆向操作。

const mementos = []
const input = document.querySelector('input')

function saveMemento() {
  mementos.push(input.value)
}

function undo() {
  const lastMemento = mementos.pop()
   
  input.value = lastMemento ? lastMemento : input.value
}
<h4> Type some characters and hit Undo </h4>
<input onkeydown="saveMemento()" value="Hello World"/>
<button onclick="undo()">Undo</button>

命令模式

对于用户执行的每个操作,您还会保存相应的 inverse action/command。例如,每次在文本框中添加字符时,都保存反函数;也就是删除那个位置的字符。

如果用户想要撤消,你弹出最后一个逆action/command并应用它。

const commands = []
const input = document.querySelector('input')

function saveCommand(e) {
  commands.push({
    // the action is also saved for implementing redo, which
    // is not implemented in this example.
    action: { type: 'add', key: e.key, index: input.selectionStart },
    inverse: { type: 'remove', index: input.selectionStart }
  })
}

function undo() {
  let value = input.value.split('')
  const lastCommand = commands.pop()
 
  if (!lastCommand) return
    
  switch (lastCommand.inverse.type) {
    case 'remove':
      value.splice(lastCommand.inverse.index, 1)
      break;      
  }
  
  input.value = value.join('')
}
<h4> Type some characters and hit Undo </h4>
<input onkeydown="saveCommand(event)" value="Hello World"/>
<button onclick="undo()">Undo</button>

我编写的代码片段仅在添加字符时有效,然后点击撤消 return 到添加字符之前的状态,因此它们过于简化了您应该如何实现这一点。

尽管如此,我认为它们展示了两种模式的核心概念。

FWIW 我在我的项目中使用 UndoManager 作为命令堆栈。