0

0

JS如何实现撤销重做

月夜之吻

月夜之吻

发布时间:2025-08-16 08:21:01

|

780人浏览过

|

来源于php中文网

原创

js实现撤销重做核心是通过命令模式维护操作历史栈;1. 使用数组存储状态历史,每次操作后推入新状态;2. 将操作封装为包含execute和undo方法的命令对象;3. 撤销时弹出当前命令并执行undo,重做时从重做栈弹出并执行execute;4. 处理复杂对象状态需使用深拷贝(如json.parse(json.stringify(obj))或_.clonedeep)避免引用共享,或采用immutable.js创建不可变数据;5. 性能优化可限制历史栈长度、采用差量更新仅记录变化部分,并结合节流防抖减少状态记录频率;6. 异步操作可通过封装promise并在命令中保存resolve/reject来实现撤销重做,无法直接撤销的操作需设计补偿机制如退款或撤销请求;该方案完整支持同步与异步操作的撤销重做,且兼顾内存与性能。

JS如何实现撤销重做

JS实现撤销重做,核心在于维护一个操作历史栈。每次操作都记录下来,撤销时回退到上一个状态,重做则前进到下一个状态。这听起来简单,但魔鬼藏在细节里,尤其是状态的管理和操作的抽象。

解决方案

  1. 状态管理: 使用一个数组来存储状态历史。每次修改后,将新的状态推入数组。
  2. 操作抽象: 将每个操作封装成一个对象,包含
    execute
    (执行)和
    undo
    (撤销)方法。
  3. 撤销和重做: 撤销时,从状态历史中弹出当前状态,并应用前一个状态。重做时,从重做栈中弹出操作,并执行它。
  4. 命令模式: 可以考虑使用命令模式来更好地组织操作。
class Command {
    constructor(execute, undo) {
        this.execute = execute;
        this.undo = undo;
    }
}

class Editor {
    constructor() {
        this.text = "";
        this.history = [];
        this.redoStack = [];
    }

    executeCommand(command) {
        command.execute();
        this.history.push(command);
        this.redoStack = []; // 清空重做栈
    }

    undo() {
        if (this.history.length === 0) return;
        const command = this.history.pop();
        command.undo();
        this.redoStack.push(command);
    }

    redo() {
        if (this.redoStack.length === 0) return;
        const command = this.redoStack.pop();
        command.execute();
        this.history.push(command);
    }
}

// 示例
const editor = new Editor();

const insertCommand = new Command(
    () => {
        const text = prompt("Enter text to insert:");
        if (text) {
            const insertIndex = editor.text.length;
            editor.text += text;
            console.log("Inserted:", text, "New text:", editor.text);
            insertCommand.undo = () => {
                editor.text = editor.text.slice(0, insertIndex);
                console.log("Undo Insert:", text, "New text:", editor.text);
            };
        } else {
            insertCommand.execute = () => {}; // 防止空输入导致问题
            insertCommand.undo = () => {};
        }
    },
    () => {} // 初始undo为空,execute中定义
);

const deleteCommand = new Command(
    () => {
        const deleteCount = parseInt(prompt("Enter number of characters to delete:"));
        if (!isNaN(deleteCount) && deleteCount > 0 && deleteCount <= editor.text.length) {
            const deletedText = editor.text.slice(editor.text.length - deleteCount);
            editor.text = editor.text.slice(0, editor.text.length - deleteCount);
            console.log("Deleted:", deletedText, "New text:", editor.text);
            deleteCommand.undo = () => {
                editor.text += deletedText;
                console.log("Undo Delete:", deletedText, "New text:", editor.text);
            };
        } else {
            deleteCommand.execute = () => {}; // 防止无效输入导致问题
            deleteCommand.undo = () => {};
        }
    },
    () => {} // 初始undo为空,execute中定义
);

// 使用示例
editor.executeCommand(insertCommand);
editor.executeCommand(deleteCommand);
editor.undo();
editor.redo();

如何处理复杂的对象状态?

处理复杂对象状态的关键在于深拷贝。 浅拷贝只会复制对象的引用,导致撤销重做时修改的是同一个对象,无法正确回退。可以使用

JSON.parse(JSON.stringify(obj))
进行简单的深拷贝,或者使用Lodash等库提供的
_.cloneDeep()
方法。 对于包含循环引用的对象,需要自定义深拷贝算法来避免栈溢出。 另外,考虑使用Immutable.js等库来创建不可变数据结构,每次修改都返回新的对象,避免直接修改原始对象,从而简化状态管理。

先锋多用户商城系统
先锋多用户商城系统

修改自网上仿乐购商城,新增功能:1、数据库在线备份与导入功能,可以随时备份数据库,数据受损可以导入数据库,确保数据安全;2、增加组合商品概念,可以用于组配商品销售(比如外套有蓝色和红色,鞋子有40码和41码等),买一送一、组合销售(比如上衣+围巾+长裙做为一个套装商品)和加价购买等销售方式;3、按照商品重量和送货距离实时计算精确运费,并可在订单中予以显示,使运费金额实现实时动态准确显示、清晰明了;

下载

性能优化:避免存储过多的状态历史

状态历史无限增长会导致内存占用过高。 可以设置状态历史的最大长度,超过限制后删除最旧的状态。 另一种优化方法是差量更新,只存储状态之间的差异,而不是完整状态。 这种方法需要仔细设计操作的结构,确保可以根据差异恢复到之前的状态。 例如,文本编辑器可以只存储插入、删除的字符和位置,而不是每次都存储整个文本。 此外,可以考虑使用节流防抖技术,避免频繁地记录状态,例如只在用户停止输入一段时间后才记录状态。

如何处理异步操作的撤销重做?

异步操作的撤销重做更加复杂,因为无法立即撤销或重做。 一种方法是将异步操作封装成Promise,并在操作对象中存储Promise的

resolve
reject
方法。 撤销时,可以
reject
当前的Promise,并重新执行之前的操作。 重做时,重新
resolve
之前的Promise。 另一种方法是使用Redux Saga或RxJS等库来管理异步操作,它们提供了更强大的撤销重做机制。 需要注意的是,某些异步操作可能无法完全撤销,例如发送电子邮件或支付交易。 在这种情况下,需要设计相应的补偿机制,例如发送撤销邮件或退款。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

408

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

532

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

309

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

74

2025.09.10

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

533

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

12

2026.01.06

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

385

2023.07.18

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

25

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号