成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

徒手?jǐn)]出Javascript 狀態(tài)管理工具 DataSet ,實(shí)現(xiàn)數(shù)據(jù)的訂閱、查詢、撤銷和恢復(fù)

suemi / 2747人閱讀

摘要:實(shí)現(xiàn)代碼大致如下回退重做操作普通操作,棧記錄,棧清空撤回操作重做操作數(shù)據(jù)的訂閱數(shù)據(jù)是以鍵值對(duì)存儲(chǔ)的,相應(yīng)地,訂閱的時(shí)候也以鍵名為準(zhǔn)。

網(wǎng)頁是用戶與網(wǎng)站對(duì)接的入口,當(dāng)我們?cè)试S用戶在網(wǎng)頁上進(jìn)行一些頻繁的操作時(shí),對(duì)用戶而言,誤刪、誤操作是一件令人抓狂的事情,“如果時(shí)光可以倒流,這一切可以重來……”。
當(dāng)然,時(shí)光不能倒流,而數(shù)據(jù)是可以恢復(fù)的,比如采用 redux(https://redux.js.org/) 來管理頁面狀態(tài),就可以很愉快地實(shí)現(xiàn)撤銷與重做,但是傲嬌的我婉拒了redux的加持,手撕出一個(gè) Javascript 狀態(tài)管理工具,鑒于是私有構(gòu)造函數(shù),怎么命名不重要,就叫他李狗蛋好了,英文名就叫 —— DataSet。

1. 數(shù)據(jù)的存儲(chǔ)

DataSet并不是被設(shè)計(jì)來存儲(chǔ)大量數(shù)據(jù)的,因此采用鍵值對(duì)的方式存儲(chǔ)也不會(huì)有任何問題,甚至連 W3C 支持的 IndexdDB 都懶得用,直接以對(duì)象存在內(nèi)存中即可,遂有:

                    // 存儲(chǔ)具體數(shù)據(jù)的容器
                    this.dataBase = {};
                    

另外,撤回與重做依賴于歷史數(shù)據(jù),因此有必要將每次改動(dòng)的數(shù)據(jù)存儲(chǔ)起來,在撤回/重做的時(shí)候按照先進(jìn)后出的規(guī)則取出,為此定義了兩個(gè)數(shù)組——撤回棧和重做棧,默認(rèn)可以往后回退100步,當(dāng)然,步長(zhǎng)可以傳入的參數(shù) undoSize 自定義:

                    // 撤回與重做棧
                    this.undoStack = new Array(options.undoSize || 100);
                    this.redoStack = new Array(options.undoSize || 100);
                    

當(dāng)然,一開始為了開發(fā)方便,有時(shí)候需要查詢數(shù)據(jù)操作歷史,因此還開辟了日志存儲(chǔ)的空間,但是目前這些日志貌似沒有派上過用場(chǎng),還白白占用內(nèi)存拖慢速度,有機(jī)會(huì)得把它移除掉。

2. 數(shù)據(jù)隔離

我們知道,Javascipt 變量實(shí)際上只是對(duì)內(nèi)存引用的一個(gè)句柄,因此當(dāng)你把對(duì)象“存”起來之后,在外部對(duì)該對(duì)象的改動(dòng)仍舊是會(huì)影響存儲(chǔ)的數(shù)據(jù)的,因此多數(shù)情況下需要對(duì)存入的對(duì)象進(jìn)行深拷貝,由于需要保存的對(duì)象通常只是用來描述狀態(tài),因此不應(yīng)包含方法,所以是可以轉(zhuǎn)為符串再存儲(chǔ)的,取用數(shù)據(jù)的時(shí)候再把它轉(zhuǎn)為對(duì)象即可,所以數(shù)據(jù)的出入分別采用了 JSON.stringify 和JSON.parse 方法。
存數(shù)據(jù):

    this.dataBase[key].value = this.immutable &&
         JSON.stringify(this.dataBase[key].value) ||
         this.dataBase[key].value;
    

取數(shù)據(jù):

    var result= (!this.mutable) &&
         JSON.parse(dataBase["" + key].value) || 
         dataBase["" + key].value;
    

鑒于部分情況下數(shù)據(jù)可以不進(jìn)行隔離,比如存儲(chǔ)AJAX獲取到的數(shù)據(jù),為此我預(yù)留了 immutable 參數(shù),這個(gè)值為真的時(shí)候存取數(shù)據(jù)不需要經(jīng)過字符串的轉(zhuǎn)換,有助于提高運(yùn)行效率。

3. 撤回、重做棧管理

前面已經(jīng)說了棧實(shí)現(xiàn)的中心思想——先進(jìn)后出,因此數(shù)據(jù)發(fā)生變化的時(shí)候,視情況對(duì)兩個(gè)數(shù)組進(jìn)行操作,采用數(shù)組的 push 方法存入,用 pop 方法取出即可,每次操作前后執(zhí)行一下數(shù)組的 shift 或者 unshift方法,來保證數(shù)組長(zhǎng)度的穩(wěn)定(畢竟這個(gè)棧是假的)。實(shí)現(xiàn)代碼大致如下:

                    // 回退/重做操作
                    var undoStack = this.undoStack;
                    var redoStack = this.redoStack;
                    var undoLength = undoStack.length;
                    if(!undoFlag){
                        // 普通操作,undo棧記錄,redo棧清空
                        undoStack.shift();
                        undoStack.push(formerData);
                        if(!!redoStack.length){
                            redoStack.splice(0);
                            redoStack.length = undoLength 
                        }
                    } else if(undoFlag === 1){
                        // 撤回操作
                        redoStack.shift();
                        redoStack.push(formerData);
                    } else {
                        // 重做操作
                        undoStack.shift();
                        undoStack.push(formerData);
                    }
                    
4. 數(shù)據(jù)的訂閱

數(shù)據(jù)是以鍵值對(duì)存儲(chǔ)的,相應(yīng)地,訂閱的時(shí)候也以鍵名為準(zhǔn)。由于接觸過的諸多代碼都濫用了 jQuery 的 .on 方法,我決定自己實(shí)現(xiàn)的所有訂閱都必須是唯一的,因此這里的每個(gè)鍵名也只能訂閱一次。訂閱的接口如下:

        function subscribe(key, callback) {
                if(typeof key !== "string"){
                    console.warn("DataSet.prototype.subscribe: required a "key" as a string.");
                    return null;
                }

                if(callback && callback instanceof Function){
                    try{
                        if(this.hasData(key)){
                            this.dataBase[key].subscribe = callback;
                        } else {
                            var newData = {};
                            newData["" + key] = null;
                            this.setData(newData, false);
                            this.dataBase[key].subscribe = callback;
                        }
                    } catch (err) {

                    }
                }

                return null;
            };
            

這樣就把回調(diào)函數(shù)與鍵名綁定了,對(duì)應(yīng)數(shù)據(jù)發(fā)生改變的時(shí)候,即執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù):

            ... 數(shù)據(jù)發(fā)生了改動(dòng)
            // 如果該data被設(shè)置訂閱,執(zhí)行訂閱回調(diào)函數(shù)
            var subscribe = dataBase[key].subscribe;
            (!BETA_silence) && (subscribe instanceof Function) && (subscribe(newData, ver));

你可能注意到了這里有個(gè) BETA_silence 參數(shù)。這是為了方法復(fù)用而預(yù)留的參數(shù),適用于數(shù)據(jù)已在外部修改的情形,只需在內(nèi)部同步一下數(shù)據(jù)即可,觸發(fā)訂閱可能引起bug,此時(shí)將 silence 設(shè)為true即可。不過我認(rèn)為應(yīng)當(dāng)盡量減少方法內(nèi)部的判斷,因此 silence 添加了 BETA_ 前綴,提醒自己有時(shí)間的話還是另增一個(gè)專門的方法。

以上基本概括 DataSet 的設(shè)計(jì)思想,剩下的就是更加具體的實(shí)現(xiàn)和接口的設(shè)計(jì),就不再細(xì)說,下面貼出完整代碼,實(shí)現(xiàn)有些倉(cāng)促,歡迎批評(píng)與指正。
代碼:

        /**
         * @constructor DataSet 數(shù)據(jù)集管理
         * @description 對(duì)數(shù)據(jù)的所有修改歷史進(jìn)行記錄,提供撤回、重做等功能
         * @description 內(nèi)部采用 JSON.stringify 和 JSON.parse對(duì)對(duì)象進(jìn)行引用隔離,因此存在性能問題,不適用于大規(guī)模的數(shù)據(jù)存儲(chǔ)
         * */
        function DataSet(param){
            return this._init(param);
        }

        !function(){
            "use strict""
            /**
             * @method 初始化
             * @param {Object} options 配置項(xiàng)
             * @return {Null}
             * */
            DataSet.prototype._init = function init(options) {
                try{
                    // 存儲(chǔ)具體數(shù)據(jù)的容器
                    this.dataBase = {};

                    // 日志存儲(chǔ)
                    this.log = [
                        {
                            action: "initial",
                            data: JSON.stringify(options).substr(137) + "...",
                            success: true
                        },
                    ];

                    // 撤回與重做棧
                    this.undoStack = new Array(options.undoSize || 100);
                    this.redoStack = new Array(options.undoSize || 100);

                    this.mutable = !!options.mutable;

                    // 初始化的時(shí)候可以傳入原始值
                    if(options.data){
                        this.setData(options.data);
                    }
                } catch(err) {
                    this.log = [
                        {
                            action: "initial",
                            data: "error:" + err,
                            success: false
                        },
                    ]  // 操作日志
                }
                return this;
            };

            /**
             * @method 設(shè)置數(shù)據(jù)
             * @param {Object|JSON} data 數(shù)據(jù)必須以鍵值對(duì)格式傳入,數(shù)據(jù)只能是純粹的Object或Array,不能有循環(huán)引用、不能有方法和Symbol
             * @param {Number|*} [undoFlag] 用來標(biāo)識(shí)對(duì)歷史棧的更改, 1-undo 2-redo 0|undefined-just 默認(rèn)不進(jìn)行棧操作
             * @param {Boolean} [BETA_silence] 靜默更新,即不觸發(fā)訂閱事件,該方法不夠安全,慎用
             * @return {Boolean} 以示成敗
             * */
            DataSet.prototype.setData = function setData(data, undoFlag, BETA_silence) {
                // try{
                    var val = null;
                    try {
                        val = JSON.stringify(data);
                    }catch(err) {
                        console.error("DataSet.prototype.setData: the data cannot be parsed to JSON string!");
                        return false;
                    }
                    var dataBase = this.dataBase;
                    var formerData = {};
                    for(var handle in data) {
                        var key = "" + handle;
                        var immutable = !this.mutable;
                        // 保存到撤回/重做棧
                        var thisData = dataBase[key];
                        var newData = immutable && JSON.parse(JSON.stringify(data[key])) || data[key];
                        if(this.dataBase[key]){
                            formerData[key] = immutable &&
                             JSON.parse(JSON.stringify(this.dataBase[key].value)) ||
                              this.dataBase[key].value;
                              
                            // 撤回時(shí)版本號(hào)減一,否則加一
                            var ver = thisData.version + ((undoFlag !== 1) && 1 || -1);  
                            dataBase[key].value = newData;
                            dataBase[key].version = ver;

                            // 如果該data被設(shè)置訂閱,執(zhí)行訂閱回調(diào)函數(shù)
                            var subscribe = dataBase[key].subscribe;
                            (!BETA_silence) &&
                            (subscribe instanceof Function) &&
                            (subscribe(newData, ver));
                        } else {
                            this.dataBase[key] = {
                                origin: newData,
                                version: 0,
                                value: newData,
                            }
                        }
                    }

                    // 回退操作
                    var undoStack = this.undoStack;
                    var redoStack = this.redoStack;
                    var undoLength = undoStack.length;
                    if(!undoFlag){
                        // 普通操作,undo棧記錄,redo棧清空
                        undoStack.shift();
                        undoStack.push(formerData);
                        if(!!redoStack.length){
                            redoStack.splice(0);
                            redoStack.length = undoLength;
                        }
                    } else if(undoFlag === 1){
                        // 撤回操作
                        redoStack.shift();
                        redoStack.push(formerData);
                    } else {
                        // 重做操作
                        undoStack.shift();
                        undoStack.push(formerData);
                    }

                    // 記錄操作日志
                    this.log.push({
                        action: "setData",
                        data: val.substr(137) + "...",
                        success: true
                    });

                    return true;
                // } catch (err){
                //     // 記錄失敗日志
                //     this.log.push({
                //         action: "setData",
                //         data: "error:" + err,
                //         success: false
                //     });
                //
                //     throw new Error(err);
                // }
            };

            /**
             * @method 獲取數(shù)據(jù)
             * @param {String|Array} param
             * @return {Object|*} 返回?cái)?shù)據(jù)依原始數(shù)據(jù)而定
             * */
            DataSet.prototype.getData = function getData(param) {
                try{
                    var dataBase = this.dataBase;

                    /**
                     * @function 獲取單個(gè)數(shù)據(jù)
                     * */
                    var getItem = function getItem(key) {
                        var data = undefined;

                        try{
                            data = (!this.mutable) && 
                                JSON.parse(JSON.stringify(dataBase["" + key].value)) ||
                                dataBase["" + key].value;
                        } catch(err){
                        }

                        return data;
                    };

                    var result = [];

                    if(/string|number/.test(typeof param)){
                        result = getItem(param);
                    } else if(param instanceof Array){
                        result = [];
                        for(var cnt = 0; cnt < param.length; cnt++) {
                            if(/string|number/.test(typeof param[cnt])) {
                                result.push(getItem(param[cnt]))
                            }else {
                                console.error("DataSet.prototype.getData: requires param(s) ,which typeof string|Number");
                            }
                        }
                    } else {
                        console.error("DataSet.prototype.getData: requires param(s) ,which typeof string|Number");
                    }

                    this.log.push({
                        action: "getData",
                        data: JSON.stringify(result || []).substr(137) + "...",
                        success: true
                    });

                    return result;
                } catch(err) {
                    this.log.push({
                        action: "getData",
                        data: "error:" + err,
                        success: false
                    });
                    console.error(err);

                    return false;
                }
            };

            /**
             * @method 判斷DataSet中是否有某個(gè)鍵
             * @param {String} key
             * @return {Boolean}
             * */
            DataSet.prototype.hasData = function hasData(key) {
                return this.dataBase.hasOwnProperty(key);
            };

            /**
             * @method 撤回操作
             * */
            DataSet.prototype.undo = function undo() {
                var self = this;
                var undoStack = self.undoStack;

                // 獲取上一次的操作
                var curActive = undoStack.pop();
                undoStack.unshift(null);

                // 撤回生效
                if(curActive){
                    self.setData(curActive, 1);
                    return true;
                }
                return null;
            };

            /**
             * @method 重做操作
             * */
            DataSet.prototype.redo = function redo() {
                var self = this;
                var redoStack = self.redoStack;
                redoStack.unshift(null);
                var curActive = redoStack.pop();

                // 重做生效
                if(curActive){
                    this.setData(curActive, 2);
                    return true;
                }
                return null;
            };

            /**
             * @method 訂閱數(shù)據(jù)
             * @description 注意每個(gè)key只能被訂閱一次,多次訂閱將只有最后一次生效
             * @param {String} key
             * @param {Function} callback 在訂閱的值發(fā)生變化的時(shí)候執(zhí)行,參數(shù)為所訂閱的值
             * @return {Null}
             * */
            DataSet.prototype.subscribe = function subscribe(key, callback) {
                if(typeof key !== "string"){
                    console.warn("DataSet.prototype.subscribe: required a "key" as a string.");
                    return null;
                }

                if(callback && callback instanceof Function){
                    try{
                        if(this.hasData(key)){
                            this.dataBase[key].subscribe = callback;
                        } else {
                            var newData = JSON.parse("{"" + key + "":null}");
                            this.setData(newData, false);
                            this.dataBase[key].subscribe = callback;
                        }
                    } catch (err) {

                    }
                }

                return null;
            };
            
            return null;
        }();

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103250.html

相關(guān)文章

  • 用Kolb學(xué)習(xí)模型來學(xué)編程

    摘要:會(huì)用其它人的分析結(jié)果,并付諸實(shí)踐,更偏向于執(zhí)行,通過錯(cuò)誤來學(xué)習(xí)。四語言學(xué)習(xí)的方法有些人可能通過感受和觀察就能很好的學(xué)習(xí)了,比如我們所熟知的一些學(xué)霸。 小推廣講堂《60分鐘徒手?jǐn)]出Spring框架》,別只會(huì)用,干脆自己擼一個(gè)輪子吧 一 前言 1984年, 大衛(wèi)·庫(kù)伯曾在他的著作《體驗(yàn)學(xué)習(xí):體驗(yàn)——學(xué)習(xí)發(fā)展的源泉》提出了學(xué)習(xí)圈理論,與他認(rèn)為經(jīng)驗(yàn)學(xué)習(xí)過程是由四個(gè)適應(yīng)性學(xué)習(xí)階段構(gòu)成的環(huán)形結(jié)構(gòu),...

    Flands 評(píng)論0 收藏0
  • Spring Cloud Hystrix入門Hystrix命令原理分析

    摘要:系統(tǒng)需要支持命令的撤銷。第步計(jì)算斷路器的健康度會(huì)將成功失敗拒絕超時(shí)等信息報(bào)告給斷路器,斷路器會(huì)維護(hù)一組計(jì)數(shù)器來統(tǒng)計(jì)這些數(shù)據(jù)。第步,當(dāng)前命令的線程池請(qǐng)求隊(duì)列或者信號(hào)量被占滿的時(shí)候。 斷路由器模式 在分布式架構(gòu)中,當(dāng)某個(gè)服務(wù)單元發(fā)生故障之后,通過斷路由器的故障監(jiān)控(類似熔斷保險(xiǎn)絲),向調(diào)用方返回一個(gè)錯(cuò)誤響應(yīng),而不是長(zhǎng)時(shí)間的等待。這樣就不會(huì)使得線程因調(diào)用故障服務(wù)被長(zhǎng)時(shí)間占用不釋放,避免了故障...

    Betta 評(píng)論0 收藏0
  • React組件設(shè)計(jì)實(shí)踐總結(jié)05 - 狀態(tài)管理

    摘要:要求通過要求數(shù)據(jù)變更函數(shù)使用裝飾或放在函數(shù)中,目的就是讓狀態(tài)的變更根據(jù)可預(yù)測(cè)性單向數(shù)據(jù)流。同一份數(shù)據(jù)需要響應(yīng)到多個(gè)視圖,且被多個(gè)視圖進(jìn)行變更需要維護(hù)全局狀態(tài),并在他們變動(dòng)時(shí)響應(yīng)到視圖數(shù)據(jù)流變得復(fù)雜,組件本身已經(jīng)無法駕馭。今天是 520,這是本系列最后一篇文章,主要涵蓋 React 狀態(tài)管理的相關(guān)方案。 前幾篇文章在掘金首發(fā)基本石沉大海, 沒什么閱讀量. 可能是文章篇幅太長(zhǎng)了?掘金值太低了? ...

    ideaa 評(píng)論0 收藏0
  • 設(shè)計(jì)模式總結(jié)

    摘要:,命令模式,將行為請(qǐng)求者和行為實(shí)現(xiàn)者解耦,將行為抽象為對(duì)象。解釋器模式,迭代器模式,將集合對(duì)象的存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)職責(zé)分離。即將遍歷的責(zé)任交給迭代器返回的迭代器,迭代器。 設(shè)計(jì)模式總結(jié) 創(chuàng)建型:除了直接new來實(shí)例化對(duì)象外,提供了多種隱藏創(chuàng)建邏輯的生成對(duì)象的方法 結(jié)構(gòu)型:通過對(duì)象和類的組合,得到新的結(jié)構(gòu)和功能 行為型:解決對(duì)象之間的通行和功能職責(zé)分配 詳細(xì)分類 工廠 簡(jiǎn)單工廠...

    quietin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<