撤銷和回退的實現

春去春又來發表於2019-03-03

功能點描述

大家應該都使用過瀏覽器的後退和前進功能,它就是我們今天的主題:撤銷和回退。其實不光是在瀏覽器裡面,在眾多的工具軟體內也都有類似的功能,遠的不說,例如:vscode、ppt。

實現思路

在說實現思路之前,我先上一張圖:

撤銷和回退的實現

眼尖的同學可能已經看到了 Stack ,是的,你想的沒錯,其實撤銷和回退功能就是對 Stack 的使用場景之一。
首先我們需要兩個 Stack(可撤銷棧和可回退棧)

為了更直觀的展示,我用點選小球代替了對網站的訪問。

A 為紅色小球;B 為綠色小球;C 為藍色小球

  1. 當點選 A 之後再點選 B,把 A 和 B 新增到‘可撤銷棧’(等同於先訪問了 A 網站,後又訪問了 B 網站),當前展示的為 B
撤銷和回退的實現
  1. 當執行撤銷功能時讓‘可撤銷棧’出棧並且把出棧的 B 新增到‘可回退棧’內,當前展示的為 A
撤銷和回退的實現

3. 當執行回退功能時讓‘可回退棧’出棧並且把出棧的 B 新增到‘可撤銷棧’內,當前展示的為 B

撤銷和回退的實現

4. 當我們訪問了 A 和 B 之後,這時候撤銷了 B ,回到了 A ,並且又點選了 C ,這時候我們需要清空‘可回退棧’,並且把 C 新增到‘可撤銷棧’

撤銷和回退的實現

code

先實現一個特殊的棧

為什麼叫它是特殊的棧呢,因為傳統的棧是沒有 #shift() 功能的。那這裡為什麼需要有這個函式呢,請帶著問題繼續往下看。

這裡偷懶直接使用了 Array 的內建函式,傳統上來講的話不應該使用 – -#

     function createStack() {
                var list = [];
                return {
                    /**
                     * 入棧
                     * @param {*} data
                     */
                    push(data) {
                        list.push(data);
                    },

                    /**
                     * 出棧
                     */
                    pop() {
                        return list.pop();
                    },

                    size() {
                        return list.length;
                    },

                    empty() {
                        return list.length === 0;
                    },

                    clear() {
                        list = [];
                    },
                    //刪除頭結點(棧底)
                    shift() {
                        list.shift();
                    },

                    peek() {
                        return list[list.length - 1];
                    },

                    getList() {
                        return list;
                    }
                }
            }
複製程式碼

record物件

     function createRecord() {
                let undoStack = createStack();
                let rollbackStack = createStack();
                const MAX_LIMIT = 6;//最大限制點
                return {
                    //獲取可撤銷棧 棧頂的資料
                    //用於展示
                    getTopValue() {
                        return undoStack.peek();
                    },
                    //新增記錄
                    //把資料直接新增到可撤銷棧內
                    //並且清空可回退棧
                    addRecord(data) {
                        //當可撤銷棧的大小大於最大的限制的話
                        //那麼需要刪除頭結點
                        if (undoStack.size() >= MAX_LIMIT) {
                            undoStack.shift();
                        }
                        undoStack.push(data);
                        rollbackStack.clear();
                    },
                    //撤銷
                    //檢測可撤銷棧是否為空,為空的話什麼也不做
                    //不然把可撤銷棧出棧的資料新增到可回退棧內
                    undoRecord() {
                        if (undoStack.empty()) return;
                        const data = undoStack.pop();
                        rollbackStack.push(data);
                    },
                    //回退
                    //檢測可回退棧是否為空,為空的話什麼也不做
                    //把可回退棧出棧的資料新增到可撤銷棧內
                    rollbackRecord() {
                        if (rollbackStack.empty()) return;
                        const data = rollbackStack.pop();
                        undoStack.push(data);
                    },
                    getUndoStack() {
                        return undoStack.getList();
                    },
                    getrollbackStack() {
                        return rollbackStack.getList();
                    }
                }
            }
複製程式碼

我們在使用 vscode 寫程式碼的時候有沒有發現撤銷到一定的數量時,就撤銷不回去了。是的,這個邏輯就是在 #addRecord() 內處理的。當‘可撤銷棧’的大小大於限制數時,那麼需要拋棄掉最初的資料,也就是刪除頭結點。這也是說明為什麼我們實現的 Stack 內有 #shift() 功能。

總結

以上的邏輯我也是在公司專案邏輯中抽離出來的,因為接手的好幾個專案都要實現此功能。當然了公司專案中我又加了 Command 模式來搭配使用,如果大家有興趣的話,後續我可以在更新如何搭配 Command 模式使用。最後想說的是,資料結構和演算法誰學誰知道,真香。

連結

程式碼連結

Demo展示連結

如果此教程對你有幫助的話,請賞賜一個小星星 -_-!

相關文章