フリーハンドはJavascript状態管理ツールDataSetを抜粋して、データの購読、検索、取り消しと回復を実現します.

15219 ワード

ウェブページはユーザーとウェブサイトのドッキングの入り口で、ユーザーがウェブページで頻繁に操作することを許可する時、ユーザーにとって、誤りを削除して、誤って操作するのは1件の気が狂う事で、“もし時間は逆流することができるならば、このすべては再び来ます…”.もちろん、時間は戻せません.データは回復できます.例えば、reduxを採用します.(https://redux.js.org/)ページの状態を管理するために、楽しく取り消しとやり直しができますが、ツンデレの私はreduxの加持をやんわり拒否しました.Javascript状態管理ツールを引き裂きました.私有構造関数であるために、どう名前を付けるかは重要ではないです.
1.データの記憶
DataSetは大量のデータを格納するように設計されているわけではないので、キーパッドを使って保存しても問題がない.W 3 CでサポートされているIndexdDBさえ使いにくく、直接対象にメモリがあれば大丈夫です.
                    //          
                    this.dataBase = {};
                    
また、撤回とやり直しは履歴データに依存するため、変更されたデータを毎回保存し、撤回・やり直しの際に先進的な後出しルールに従って取り出す必要があり、そのために2つの配列を定義しています.
                    //       
                    this.undoStack = new Array(options.undoSize || 100);
                    this.redoStack = new Array(options.undoSize || 100);
                    
もちろん、最初は開発のために、データ操作の歴史を調べる必要があります.だから、ログ記憶の空間も開いています.しかし、今のところこれらのログは役に立ちません.メモリを使って、速度を遅くして、削除する機会があります.
2.データ分離
Javascipt変数は実際にはメモリに対してのみ引用されるものであることを知っています.したがって、オブジェクトを「保存」するときには起きてからも、外部でその対象に対する変更は依然として格納に影響を及ぼすので、多くの場合、預け入れた対象を深くコピーする必要があります.保存が必要な対象は通常状態を記述するためにのみ使用されます.方法は含まれてはいけません.だから、データを串に戻して保存してもいいです.データを取る時は対象に回せばいいです.データの出入りは可能です.JSON.strigifyとJSON.parseの方法をそれぞれ採用しています.データの保存:
    this.dataBase[key].value = this.immutable &&
         JSON.stringify(this.dataBase[key].value) ||
         this.dataBase[key].value;
    
データを取る:
    var result= (!this.mutable) &&
         JSON.parse(dataBase['' + key].value) || 
         dataBase['' + key].value;
    
一部の状況に鑑みてデータを分離しなくてもいいです.例えばAJAXで取得したデータを記憶しています.そのためにimmutableパラメータを残しておきました.この値が本当の場合、アクセスデータは文字列で変換する必要がなく、運行効率を向上させるのに役立ちます.
3.取下げ、再構築スタック管理
前に、スタックの実現の中心思想である先進的なポストからデータが変化した時に、状況に応じて2つの配列を操作して、配列のpush方法で預け入れて、pop方法で取り出してもいいです.操作の前後に配列のshiftまたはunshift方法を実行して、配列長さの安定を保証します.実現コードは大体以下の通りです.
                    //   /    
                    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.データの購読
データはキーの値で保存されています.したがって、購読時もキーの名前を基準にしています.接触したコードの多くはjQueryを乱用しています.on方法は、自分が実現したすべての購読が唯一であることを決めました.ここのキーの名前は一回しか購読できません.購読のインターフェイスは以下の通りです.
        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;
            };
            
このようにして、コールバック関数をキーと結びつけて、対応するデータが変化した時、すなわち対応するコールバック関数を実行します.
            ...        
            //    data     ,        
            var subscribe = dataBase[key].subscribe;
            (!BETA_silence) && (subscribe instanceof Function) && (subscribe(newData, ver));
ここにBETA USilenceパラメータがあることに気づいたかもしれません.これは方法多重のために予約されたパラメータです.データが外部で修正された場合に適用されます.内部でデータを同期させるだけでいいです.購読をトリガしてバグを引き起こす可能性があります.この時はsilenceをtrueに設定すればいいです.私は方法内部の判断をできるだけ減らすべきだと思います.したがって、silenceはBETA Atuuプレフィックスを追加しました.自分に時間があるように注意してください.専門的な方法を追加します.
以上の基本的な要約DataSetの設計思想は、より具体的な実現とインターフェースの設計は、もはや詳細には、次の完全なコードを貼り付け、いくつかの緊急性を実現し、批判と指摘のコードを歓迎します.
        /**
         * @constructor DataSet      
         * @description               ,    、     
         * @description      JSON.stringify   JSON.parse         ,        ,            
         * */
        function DataSet(param){
            return this._init(param);
        }

        !function(){
            'use strict''
            /**
             * @method    
             * @param {Object} options    
             * @return {Null}
             * */
            DataSet.prototype._init = function init(options) {
                try{
                    //          
                    this.dataBase = {};

                    //     
                    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;

                    //              
                    if(options.data){
                        this.setData(options.data);
                    }
                } catch(err) {
                    this.log = [
                        {
                            action: 'initial',
                            data: 'error:' + err,
                            success: false
                        },
                    ]  //     
                }
                return this;
            };

            /**
             * @method     
             * @param {Object|JSON} data             ,        Object Array,       、      Symbol
             * @param {Number|*} [undoFlag]            , 1-undo 2-redo 0|undefined-just         
             * @param {Boolean} [BETA_silence]     ,        ,       ,  
             * @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;
                              
                            //         ,    
                            var ver = thisData.version + ((undoFlag !== 1) && 1 || -1);  
                            dataBase[key].value = newData;
                            dataBase[key].version = ver;

                            //    data     ,        
                            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     
             * @param {String|Array} param
             * @return {Object|*}            
             * */
            DataSet.prototype.getData = function getData(param) {
                try{
                    var dataBase = this.dataBase;

                    /**
                     * @function       
                     * */
                    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       
             * @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     
             * @description     key       ,             
             * @param {String} key
             * @param {Function} callback               ,        
             * @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;
        }();