如何做撤销/重做功能

时间:2019-01-29 08:01:03

标签: javascript undo-redo

我想在脚本中添加一个undo/redo函数。我环顾四周,发现了一些建议,其中大多数建议使用command pattern

我搜索了例子,教程或其他说明其工作原理的东西,但找不到任何东西。

该函数必须在一页上工作,我的意思是在重新加载该页面之后,该函数必须能够redo/undo进行最后的思考。

嗯,我不知道命令模式的工作原理,我想创建一个对象,以存储函数的名称,旧值和新值-但我不确定这是否有效做到与否的方法。

也许有人可以给我一个小例子,说明undo/redo函数的代码应该如何寻找。

1 个答案:

答案 0 :(得分:5)

您通常有2种选择:

纪念图案

  • 易于实现,在许多情况下内存效率低。

在应用操作之前,请对当前状态进行快照并将其保存到数组中。该快照是 Memento

如果用户要撤消操作,只需pop最后一个纪念品并应用它。程序返回到执行上一个操作之前的状态。

此模式在效率方面受到限制。每个纪念品都相对较大,因为它可以捕获整个当前状态。

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

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 动作(命令)。例如,每次在文本框中添加字符时,都会保存反函数;就是删除该位置的字符。

如果用户要撤消操作,则可以应用相反的操作。

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>

我编写的摘录仅在添加字符时起作用,然后单击undo以返回添加字符之前的状态,因此它们过于简化了应如何实现。

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

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