如何制作撤消/重做功能

问题描述 投票:0回答:1

我想在我的脚本中添加一个

undo/redo
函数。我环顾四周,看到了一些建议,其中大多数建议使用
command pattern

该函数必须在一页上运行 - 重新加载页面后,该函数必须能够

redo/undo
最后的事情。

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

也许有人可以给我一个小例子,

undo/redo
函数的代码应该是什么样子。

javascript undo-redo
1个回答
63
投票

有两种常见的实现撤消/重做的方法,这两种方法都非常完善和定义设计模式。

备忘录模式命令模式

纪念品模式,您可以在其中捕获整个当前状态

备忘录模式是一种软件设计模式,它揭示了 对象的私有内部状态。一个例子说明这是如何实现的 用于将对象恢复到之前的状态(通过撤消 回滚),另一个是版本控制,另一个是自定义序列化。

它很容易实现,但内存效率低下,因为您需要存储整个状态的类似副本。

  • 命令模式,您可以在其中捕获影响状态的命令/操作当前动作和它的逆动作

在面向对象编程中,命令模式是一种行为模式 使用对象来封装所有内容的设计模式 稍后执行操作或触发事件所需的信息 时间。这些信息包括方法名称、拥有的对象 方法和方法参数的值。

这更难实现,因为对于应用程序中的每个可撤消操作,您必须显式编码它的逆操作,但它的内存效率更高,因为您只存储影响状态的操作。

我的轻率建议是这样的:

如果您正在构建一个供自己使用的小工具,并且您只是想要一种快速而肮脏的方法来完成此任务,请选择纪念品模式。易于实施并且有效。也许您只是想让用户撤消用户流程中的某些选择?纪念品图案是完美的选择。

现在,如果您正在为具有一定复杂性及以上复杂性的复杂系统构建撤消机制,那么您将必须选择命令模式;这几乎是理所当然的,否则你会在不知不觉中耗尽内存。

我将进一步详细说明:

纪念品图案

在应用操作之前,您可以拍摄当前状态的快照并将其保存到数组中。那张快照就是纪念品

如果用户想要撤消,您只需

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>

我编写的代码片段仅在添加字符时起作用,然后单击撤消以返回到添加字符之前的状态,因此它们对您应该如何实现这一点过于简单化。

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

FWIW 我在项目中使用

UndoManager

作为命令堆栈。

© www.soinside.com 2019 - 2024. All rights reserved.