备忘录模式核心解析

  1. 备忘录模式 (Memento Pattern)
    1. 核心目的是在不破坏对象封装性的前提下,捕获并保存对象的内部状态,以便后续需要时能将对象恢复到该状态;
    2. 可以用生活中的场景类比:
      • 玩游戏时的 “存档”:游戏角色 (原发器) 的血量、等级、装备等状态被保存到存档文件 (备忘录),后续可以读档恢复状态;
      • 文档编辑的 “撤销”:编辑器 (原发器) 每次输入后保存当前内容 (备忘录),点击撤销就能回到上一个状态;
  2. 类图:
  3. 实现代码:
    // 1. 备忘录类(Memento):存储编辑器的状态,仅暴露获取状态的方法
    class EditorMemento {
      private state: string; // 存储的编辑器内容(私有,外部无法修改)
    
      // 构造函数:初始化备忘录状态
      constructor(state: string) {
        this.state = state;
      }
    
      // 获取存储的状态(给原发器恢复状态用)
      public getState(): string {
        return this.state;
      }
    }
    
    // 2. 原发器类(Originator):编辑器本身,拥有状态并能创建/恢复备忘录
    class TextEditor {
      private content: string; // 编辑器当前内容(核心状态)
    
      constructor() {
        this.content = "";
      }
    
      // 设置编辑器内容(修改状态)
      public setContent(content: string): void {
        this.content = content;
        console.log(`编辑器当前内容:${this.content}`);
      }
    
      // 获取当前内容(辅助方法)
      public getContent(): string {
        return this.content;
      }
    
      // 创建备忘录:保存当前状态
      public createMemento(): EditorMemento {
        console.log("📌 保存当前编辑状态");
        return new EditorMemento(this.content);
      }
    
      // 恢复备忘录:回到指定状态
      public restoreMemento(memento: EditorMemento): void {
        this.content = memento.getState();
        console.log(`🔙 恢复到历史状态:${this.content}`);
      }
    }
    
    // 3. 管理者类(Caretaker):管理备忘录(撤销栈),不操作状态仅存储
    class UndoManager {
      private mementos: EditorMemento[]; // 存储所有备忘录的栈
    
      constructor() {
        this.mementos = [];
      }
    
      // 添加备忘录到栈中(每次编辑后保存)
      public addMemento(memento: EditorMemento): void {
        this.mementos.push(memento);
      }
    
      // 获取指定索引的备忘录(撤销时取上一个)
      public getMemento(index: number): EditorMemento | undefined {
        return this.mementos[index];
      }
    
      // 获取最后一个备忘录(简化撤销逻辑)
      public getLastMemento(): EditorMemento | undefined {
        return this.mementos.pop();
      }
    }
    
    // 🌟 测试示例:模拟编辑器撤销操作
    const editor = new TextEditor();
    const undoManager = new UndoManager();
    
    // 第一步:输入第一段内容,保存状态
    editor.setContent("Hello World!");
    undoManager.addMemento(editor.createMemento());
    
    // 第二步:输入第二段内容,保存状态
    editor.setContent("Hello World! 我是备忘录模式");
    undoManager.addMemento(editor.createMemento());
    
    // 第三步:输入第三段内容(错误输入)
    editor.setContent("Hello World! 我是备忘录模式(错误修改)");
    
    // 第四步:撤销操作,恢复到上一个正确状态
    const lastMemento = undoManager.getLastMemento();
    if (lastMemento) {
      editor.restoreMemento(lastMemento);
    }
    
    // 第五步:再次撤销,恢复到最初状态
    const firstMemento = undoManager.getLastMemento();
    if (firstMemento) {
      editor.restoreMemento(firstMemento);
    }
    

常用使用场景

表单的撤销

  1. 业务场景:用户填写表单时误操作 (如删错内容、选错选项),支持撤销到上一步状态;

  2. 实现代码:

    // 1. 备忘录:存储表单状态
    class FormMemento {
      private state: Record<string, any>; // 表单状态(支持多字段)
    
      constructor(state: Record<string, any>) {
        // 深拷贝避免引用类型修改原数据
        this.state = JSON.parse(JSON.stringify(state));
      }
    
      getState(): Record<string, any> {
        return JSON.parse(JSON.stringify(this.state)); // 防止外部修改
      }
    }
    
    // 2. 原发器:表单核心逻辑
    class FormEditor {
      private formState: Record<string, any> = {
        username: "",
        phone: "",
        address: "",
        agree: false
      };
    
      // 更新表单字段
      updateField(field: string, value: any): void {
        this.formState[field] = value;
        console.log(`✅ 表单更新:${field} = ${value}`);
        console.log("当前表单状态:", this.formState);
      }
    
      // 创建备忘录
      createMemento(): FormMemento {
        console.log("📌 保存表单状态");
        return new FormMemento(this.formState);
      }
    
      // 恢复表单状态
      restoreMemento(memento: FormMemento): void {
        this.formState = memento.getState();
        console.log("🔙 撤销表单操作,恢复状态:", this.formState);
      }
    
      getFormState() {
        return this.formState;
      }
    }
    
    // 3. 管理者:管理表单撤销栈
    class FormUndoManager {
      private mementos: FormMemento[] = [];
    
      addMemento(memento: FormMemento): void {
        this.mementos.push(memento);
      }
    
      getLastMemento(): FormMemento | undefined {
        return this.mementos.pop();
      }
    }
    
    // 测试:表单撤销
    console.log("===== 场景1:表单撤销 =====");
    const form = new FormEditor();
    const formUndo = new FormUndoManager();
    
    // 步骤1:填写用户名
    form.updateField("username", "zhangsan");
    formUndo.addMemento(form.createMemento());
    
    // 步骤2:填写手机号
    form.updateField("phone", "13800138000");
    formUndo.addMemento(form.createMemento());
    
    // 步骤3:误填地址
    form.updateField("address", "错误地址");
    
    // 步骤4:撤销地址错误操作
    const lastMemento = formUndo.getLastMemento();
    if (lastMemento) form.restoreMemento(lastMemento);
    

页面状态恢复

  1. 业务场景:SPA 列表页筛选 / 排序后跳转到详情页,返回时恢复筛选、排序、页码状态;

  2. 实现代码:

    // 1. 备忘录:存储列表页状态
    class PageMemento {
      private state: {
        filter: Record<string, any>; // 筛选条件
        sort: { key: string; order: "asc" | "desc" }; // 排序规则
        page: number; // 页码
      };
    
      constructor(state: typeof PageMemento.prototype.state) {
        this.state = JSON.parse(JSON.stringify(state));
      }
    
      getState() {
        return JSON.parse(JSON.stringify(this.state));
      }
    }
    
    // 2. 原发器:列表页核心逻辑
    class ListPage {
      private pageState = {
        filter: { keyword: "", category: "" },
        sort: { key: "id", order: "asc" },
        page: 1
      };
    
      // 更新筛选条件
      updateFilter(filter: Record<string, any>): void {
        this.pageState.filter = { ...this.pageState.filter, ...filter };
        console.log("筛选条件更新:", this.pageState.filter);
      }
    
      // 更新排序
      updateSort(sort: { key: string; order: "asc" | "desc" }): void {
        this.pageState.sort = sort;
        console.log("排序规则更新:", this.pageState.sort);
      }
    
      // 更新页码
      updatePage(page: number): void {
        this.pageState.page = page;
        console.log("页码更新:", page);
      }
    
      // 创建备忘录(离开页面时调用)
      createMemento(): PageMemento {
        console.log("📌 保存列表页状态");
        return new PageMemento(this.pageState);
      }
    
      // 恢复备忘录(返回列表页时调用)
      restoreMemento(memento: PageMemento): void {
        this.pageState = memento.getState();
        console.log("🔙 恢复列表页状态:", this.pageState);
      }
    }
    
    // 3. 管理者:全局存储页面状态(可结合localStorage持久化)
    class PageStateManager {
      private mementoMap: Record<string, PageMemento> = {}; // 页面路径 -> 备忘录
    
      // 保存指定页面的状态
      savePageState(pageKey: string, memento: PageMemento): void {
        this.mementoMap[pageKey] = memento;
      }
    
      // 获取指定页面的状态
      getPageState(pageKey: string): PageMemento | undefined {
        return this.mementoMap[pageKey];
      }
    }
    
    // 测试:页面状态恢复
    console.log("\n===== 场景2:页面状态恢复 =====");
    const listPage = new ListPage();
    const pageManager = new PageStateManager();
    
    // 步骤1:设置筛选和排序
    listPage.updateFilter({ keyword: "手机", category: "数码" });
    listPage.updateSort({ key: "price", order: "desc" });
    listPage.updatePage(3);
    
    // 步骤2:跳转到详情页,保存列表页状态
    pageManager.savePageState("/goods/list", listPage.createMemento());
    
    // 步骤3:返回列表页,恢复状态
    const savedState = pageManager.getPageState("/goods/list");
    if (savedState) listPage.restoreMemento(savedState);
    

交互类页面的状态保存

  1. 业务场景:H5 答题游戏,用户关闭页面后重新打开,恢复答题进度、分数、当前关卡;

  2. 实现代码:

    // 1. 备忘录:存储游戏状态
    class GameMemento {
      private state: {
        level: number; // 当前关卡
        score: number; // 分数
        progress: number; // 答题进度(已答/总题数)
        answered: number[]; // 已答题目ID
      };
    
      constructor(state: typeof GameMemento.prototype.state) {
        this.state = JSON.parse(JSON.stringify(state));
      }
    
      getState() {
        return JSON.parse(JSON.stringify(this.state));
      }
    }
    
    // 2. 原发器:游戏核心逻辑
    class AnswerGame {
      private gameState = {
        level: 1,
        score: 0,
        progress: 0,
        answered: [] as number[]
      };
    
      // 答题成功
      answerSuccess(questionId: number, score: number): void {
        this.gameState.answered.push(questionId);
        this.gameState.score += score;
        this.gameState.progress = this.gameState.answered.length / 10; // 假设共10题
        console.log(`答题成功:ID=${questionId},分数+${score},当前分数=${this.gameState.score}`);
      }
    
      // 升级关卡
      nextLevel(): void {
        this.gameState.level += 1;
        console.log(`升级到${this.gameState.level}关`);
      }
    
      // 创建备忘录(持久化到localStorage)
      createMemento(): GameMemento {
        const memento = new GameMemento(this.gameState);
        // 模拟持久化存储
        localStorage.setItem("gameState", JSON.stringify(memento.getState()));
        console.log("📌 保存游戏状态到本地");
        return memento;
      }
    
      // 恢复备忘录(从localStorage加载)
      restoreMemento(memento?: GameMemento): void {
        if (!memento) {
          // 从本地加载
          const saved = localStorage.getItem("gameState");
          if (saved) this.gameState = JSON.parse(saved);
        } else {
          this.gameState = memento.getState();
        }
        console.log("🔙 恢复游戏状态:", this.gameState);
      }
    }
    
    // 3. 管理者:游戏状态管理(简化版)
    class GameStateManager {
      private memento: GameMemento | null = null;
    
      saveState(memento: GameMemento): void {
        this.memento = memento;
      }
    
      getState(): GameMemento | null {
        return this.memento;
      }
    }
    
    // 测试:游戏状态保存
    console.log("\n===== 场景3:交互类页面状态保存 =====");
    const game = new AnswerGame();
    const gameManager = new GameStateManager();
    
    // 步骤1:答题升级
    game.answerSuccess(1, 10);
    game.answerSuccess(2, 10);
    game.nextLevel();
    
    // 步骤2:保存游戏状态
    gameManager.saveState(game.createMemento());
    
    // 步骤3:重新打开游戏,恢复状态
    game.restoreMemento(gameManager.getState());
    

可视化组件的状态回退(拖拽组件)

  1. 业务场景:拖拽看板 (如 Trello 风格),拖拽卡片后支持撤销操作,恢复卡片位置;

  2. 实现代码:

    // 1. 备忘录:存储看板状态
    class KanbanMemento {
      private state: {
        cards: Record<string, { id: string; column: string; position: number }>; // 卡片位置
      };
    
      constructor(state: typeof KanbanMemento.prototype.state) {
        this.state = JSON.parse(JSON.stringify(state));
      }
    
      getState() {
        return JSON.parse(JSON.stringify(this.state));
      }
    }
    
    // 2. 原发器:看板核心逻辑
    class KanbanBoard {
      private boardState = {
        cards: {
          card1: { id: "card1", column: "todo", position: 1 },
          card2: { id: "card2", column: "todo", position: 2 }
        }
      };
    
      // 移动卡片
      moveCard(cardId: string, targetColumn: string, targetPos: number): void {
        if (this.boardState.cards[cardId]) {
          this.boardState.cards[cardId] = {
            ...this.boardState.cards[cardId],
            column: targetColumn,
            position: targetPos
          };
          console.log(`移动卡片${cardId}到${targetColumn}列,位置${targetPos}`);
          console.log("当前看板状态:", this.boardState.cards);
        }
      }
    
      // 创建备忘录
      createMemento(): KanbanMemento {
        console.log("📌 保存看板状态");
        return new KanbanMemento(this.boardState);
      }
    
      // 恢复备忘录
      restoreMemento(memento: KanbanMemento): void {
        this.boardState = memento.getState();
        console.log("🔙 撤销拖拽,恢复看板状态:", this.boardState.cards);
      }
    }
    
    // 3. 管理者:看板撤销栈
    class KanbanUndoManager {
      private mementos: KanbanMemento[] = [];
    
      addMemento(memento: KanbanMemento): void {
        this.mementos.push(memento);
      }
    
      getLastMemento(): KanbanMemento | undefined {
        return this.mementos.pop();
      }
    }
    
    // 测试:看板拖拽撤销
    console.log("\n===== 场景4:可视化组件状态回退 =====");
    const kanban = new KanbanBoard();
    const kanbanUndo = new KanbanUndoManager();
    
    // 步骤1:保存初始状态
    kanbanUndo.addMemento(kanban.createMemento());
    
    // 步骤2:误移动卡片
    kanban.moveCard("card1", "done", 1);
    
    // 步骤3:撤销拖拽操作
    const lastKanbanState = kanbanUndo.getLastMemento();
    if (lastKanbanState) kanban.restoreMemento(lastKanbanState);
    

购物车状态

  1. 业务场景:用户临时清空购物车后,支持恢复之前的购物车商品;或切换账号时暂存当前购物车状态;

  2. 实现代码:

    // 1. 备忘录:存储购物车状态
    class CartMemento {
      private state: {
        items: Array<{ id: string; name: string; count: number; price: number }>; // 购物车商品
        selected: string[]; // 选中的商品ID
      };
    
      constructor(state: typeof CartMemento.prototype.state) {
        this.state = JSON.parse(JSON.stringify(state));
      }
    
      getState() {
        return JSON.parse(JSON.stringify(this.state));
      }
    }
    
    // 2. 原发器:购物车核心逻辑
    class ShoppingCart {
      private cartState = {
        items: [
          { id: "p1", name: "手机", count: 1, price: 2999 },
          { id: "p2", name: "耳机", count: 1, price: 199 }
        ],
        selected: ["p1", "p2"]
      };
    
      // 添加商品
      addItem(item: { id: string; name: string; count: number; price: number }): void {
        this.cartState.items.push(item);
        this.cartState.selected.push(item.id);
        console.log(`添加商品:${item.name},当前购物车:`, this.cartState.items);
      }
    
      // 清空购物车
      clearCart(): void {
        this.cartState.items = [];
        this.cartState.selected = [];
        console.log("清空购物车:", this.cartState);
      }
    
      // 创建备忘录
      createMemento(): CartMemento {
        console.log("📌 保存购物车状态");
        return new CartMemento(this.cartState);
      }
    
      // 恢复备忘录
      restoreMemento(memento: CartMemento): void {
        this.cartState = memento.getState();
        console.log("🔙 恢复购物车状态:", this.cartState);
      }
    }
    
    // 3. 管理者:购物车状态管理(支持多版本)
    class CartStateManager {
      private mementos: Record<string, CartMemento> = {}; // 版本ID -> 备忘录
    
      saveCartVersion(versionId: string, memento: CartMemento): void {
        this.mementos[versionId] = memento;
      }
    
      getCartVersion(versionId: string): CartMemento | undefined {
        return this.mementos[versionId];
      }
    }
    
    // 测试:购物车状态恢复
    console.log("\n===== 场景5:购物车状态 =====");
    const cart = new ShoppingCart();
    const cartManager = new CartStateManager();
    
    // 步骤1:保存购物车初始版本
    cartManager.saveCartVersion("v1", cart.createMemento());
    
    // 步骤2:清空购物车(误操作)
    cart.clearCart();
    
    // 步骤3:恢复购物车v1版本
    const cartV1 = cartManager.getCartVersion("v1");
    if (cartV1) cart.restoreMemento(cartV1);
    

优缺点分析

  1. 优点:

    1. 封装性好:不暴露对象内部结构;
    2. 简化发起者:将状态存储职责分离出去;
    3. 易于实现撤销/重做:天然支持操作历史;
    4. 状态恢复可靠:可以精确恢复到某个历史状态;
  2. 缺点:

    1. 内存消耗:如果状态过大或保存频繁,会消耗大量内存;
    2. 维护成本:需要维护备忘录的生命周期;
    3. 可能影响性能:频繁的序列化和反序列化;

实际应用建议

  1. 适合场景

    1. 需要保存对象历史状态的场景;
    2. 需要实现撤销功能的场景;
    3. 不希望暴露对象内部细节的场景;
  2. 优化策略

    1. 增量保存:只保存变化的部分;
    2. 压缩存储:对备忘录数据进行压缩;
    3. 限制历史数量:设置最大保存数量;
    4. 懒加载:按需恢复状态;
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 备忘录模式核心解析
  2. 2. 常用使用场景
    1. 2.1. 表单的撤销
    2. 2.2. 页面状态恢复
    3. 2.3. 交互类页面的状态保存
    4. 2.4. 可视化组件的状态回退(拖拽组件)
    5. 2.5. 购物车状态
  3. 3. 优缺点分析
  4. 4. 实际应用建议