更好的共用化封裝是程序員不斷追求的目標

程序員老是在作重複性的工做,經常由於80%公用的內容,但有20%的不一樣之處,致使要重寫,或複製修改;程序員

更好的共用化封裝是程序員不斷追求的目標,設計的公用性與適用度還有效率之間要找平衡點;gulp

舉些例子,分享給新手!(示例來自個人 fixedstar 引擎)數組

1. 附加功能封包

  • 輔助功能設計儘量不影響原函數設計,也支持原函數的變更升級;緩存

  • 只關注輔助功能自己使用場景,不受業務應用場景限制;app

  • 單一功能實現,不要多功能集成;函數

示例1:

高頻觸發的行爲經常會產生沒必要要的消耗,能夠設計節流函數來控制使用頻率。工具

/**
 * 獲取在控制執行頻率的節流函數
 *
 * - 與上一次執行時間超過等待時間時能夠直接觸發;
 * - 間隔時間以內調用不會觸發,保留最後一次調用定時在等待時間後觸發;
 *
 * 可用於高頻發事件的回調,如:onresize onscroll 等等
 *
 * @param {function} cb 回調
 * @param {object} [thisObj] 回調函數的this對象
 * @param {number} [wait=0] 設置兩次執行間隔最小等待時間,沒有設置時間則爲下一幀解鎖
 * @return {function} 有限制的回調
 */
function throttle(cb, thisObj, wait) {
    if( wait===undefined ) wait = 0;
    var timer = null;
    var locked = false;
    var isfirst = true;
    var now;
    return function () {
        var args = arguments;
        var _this = this;
        // 定時執行
        var fn = function () {
            locked = false;
            isfirst = false;
            now = Date.now();
            timer = setTimeout(function () {
                locked = false;
                timer = null;
                isfirst = true;
            }, wait);
            cb.apply(thisObj || _this, args);
        };
        // 若是鎖了,說明前面有調用而且沒有到兩次間隔最小執行等待時間
        if(locked) {
            clearTimeout(timer);
            timer = null;
            // 若是到了最大等待時間,直接執行,並延時等待
            if( Date.now()-now>=wait ) {
                fn();
                now = Date.now();
            }
        } else {
            // 沒有鎖定的第一次直接執行,並延時等待
            if( isfirst ) fn();
            // 在第一次或執行後的第一次調用中,進行鎖定
            now = Date.now();
            locked = true;
        }
        // 若是沒有定時任務加上
        if( !timer ) timer = setTimeout(fn, wait-(Date.now()-now));
    };
};

上例中代碼從新編輯過,setTimeout 會影響效率,原代碼中使用 thread 進行調用,此處爲了方便演示,暫使用 setTimeout 替換,關於 thread 有時間再開新貼。測試

調用:優化

var t, st = 0;
var fn = fx.object.throttle(function (i) {
    var now = Date.now();
    console.log(i, 'ok', now-st);
    st = now;
}, null, 30, 2000);
// 下面代碼只執行 1和4,由於1是直接調用,二、三、4都在1調用以後沒有到間隔時間會一個個覆蓋,最後留下來是4,會定時間隔執行
fn(1);fn(2);fn(3);fn(4);

// 測試高頻率10ms時,仍然按照30ms間隔進行觸發
t = setInterval(fn, 10);
//clearInterval(t);
// 測試較低頻率50ms時,會按照50ms間隔進行觸發
//t = setInterval(fn, 50);
//clearInterval(t);

示例2:

如一般作優化時,須要計算代碼運行時間,工具備時不能很細緻也受到環境限制,因此不可避免須要寫一些計時的腳本調試或統計:this

/**
 * 生成一個計算函數運行時間的新函數(能夠打印或使用回調函數返回運行時間值)
 *
 * 新函數與原函數調用及功能不變;
 *
 * @param {function} cb    測試函數
 * @param {object} [thisObj=cb.this]    測試函數的 this,若是參數爲 undefined 時 this 爲函數內部綁定的this
 * @param {function} [timeCb] 用來獲取運行時間回調函數
 * @param {number} [timeCb.time] 運行時間 ms
 * @param {number} [timeCb.cb] 原函數
 * @param {...} [timeCb.args] 多參數,傳入原函數的參數
 * @return {function} 返回cb原功能函數增長了計算功能後封包的函數
 */
function runTime(cb, thisObj, timeCb) {
    var t;
    return function () {
        var args = Array.prototype.slice.call(arguments);
        t = Date.now();
        if( thisObj===undefined ) thisObj = this;
        var ret = cb.apply(thisObj, args);
        var v = Date.now()-t;
        if( timeCb ) {
            timeCb.apply(null, [v, cb].concat(args));
        } else {
            console.log(cb.name, 'runTime:', v, 'ms');
        }
        return ret;
    };
};

調用:

function sum(n) {
    var m = 0;
    for (var i = 0; i < n; i++) {
      m+=i;
    }
    return m;
 }


 function getRunTime(time) {
    if( time<1000 ) {
        console.log('運行時間1秒內能夠接受', time);
    } else {
        console.warn('運行時間超過了1秒不能接受', time);
    }
 }

 var sum2 = runTime(sum);
 // 臨時調試打印結果
 console.log(sum2(100000000));
 // 須要獲取時間處理相關邏輯
 var sum3 = runTime(sum, null, getRunTime);
 sum3(100000000);
 sum3(5000000000);
 // 若是不想在生產時使用,能夠設計運行條件,或在gulp生成時排除
 sum4 = window.isDebug ? runTime(sum) : sum;
 sum4(100000000);

2. 通用的動態處理

一般獲取數據屬性都是靜態的,也就是 o.x 相似這樣,但當數據有必定不肯定性時,如 o.point.x 這時若是 o.point 有可能爲 undefined 時,就會報錯 Cannot read property 'x' of undefined;

有人可能會問,爲何不將數據構造好,避免這樣的狀況呢!或在發現問題補上數據便可...

上面說的是靜態數據的處理方式,如系統配置數據。動態數據的區別是數據的變更性,多是用戶操做構造,多是服務端查詢數據返回,多是本地緩存須要反覆更新修改的數據等等。

動態數據會產生較多結構設計固定,但實際內容不肯定,或必定要寫成固定式的就會增長不少結構維護消耗。如:

var users = {
    user1: {
        info: {
            hobby: ['玩遊戲', '藍球', '看電影']
        },
        // ...
    },
    user2: {
        info: {
            hobby: []
        },
        // ...
    },
    user3: {}
    // ...
};

若是業務需求要將全部沒有填寫愛好的用戶收集起來,常規判斷方法是:

(試想一下,再增長些寫數據的需求,複雜度就更高了,不只須要判斷對象存在性,還要一層層建立數據,如將填寫了愛好的用戶增長系統動態評分等等)

var ret = [];
for( var k in users ) {
    var user = users[k];
    if( user && user.info && user.info.hobby ) {
        var fristHobby = user.info.hobby[0];
        if( fristHobby ) ret.push(fristHobby);
    }
}
console.log(ret);

若是你習慣這樣的多重判斷,反覆出現或在不一樣業務邏輯中返回硬性封裝而不感到厭煩,那就能夠跳過此文。

實際咱們想一次能拿到屬性,理想調用以下:

var ret = [];
for( var k in users ) {
    var fristHobby = attr(users, k + '.info.hobby[0]');
    if( fristHobby ) ret.push(fristHobby);
}
console.log(ret);

代碼實現:

/**
 * 獲取或設置對象屬性,容許多級自動建立
 *
 * @param {object} obj 須要屬性的對象,一般操做JSON
 * @param {String} name 屬性名稱(.[]是關鍵字不能夠命名),能夠多級,如:name 或 msg.title 或 msg.list[0].user
 * @param value 賦值
 * @return 返回對象屬性,未找到返回 undefined
 */
function attr(obj, name, value) {
    if (!obj || typeof obj != 'object') return undefined;
    var pAry = name.split('.');
    var key = null;
    var isSet = (arguments.length > 2);
    for (var i = 0; i < pAry.length; i++) {
        key = pAry[i].trim();
        // 若是此鍵爲數組,先要指向數組元素
        if (/^[^\[]+\[\d+\]$/.test(key)) {
            var tempAry = key.split(/[\[\]]/g);
            key = tempAry[0];
            if (!obj[key]) {
                if (!isSet) return undefined;
                obj[key] = [];
            }
            obj = obj[key];
            key = parseInt(tempAry[1]);
        }
        // 而後判斷是否最後一級,則直接賦值 結束
        if (i >= pAry.length - 1) {
            if (!isSet) return obj[key];
            obj[key] = value;
            break;
        }
        // 不然還有下級,則指向下級對象
        if (!obj[key] || typeof obj[key] != 'object') {
            if (!isSet) return undefined;
            obj[key] = {};
        }
        obj = obj[key];
    }
    return value;
};

使用這個封裝完成用戶增長系統動態評分就簡單了,此方法支持多級寫數據:

var ret = [];
for( var k in users ) {
    var n = 0;
    
    var fristHobby = attr(users, k + '.info.hobby[0]');
    if( fristHobby ) {
        ret.push(fristHobby);
        n++;
    }
    
    var score = attr(users, k + '.info.score');
    attr(users, k + '.info.score', score ? score + n : n);
}
console.log(ret, users);

更多例子

未完待續...

相關文章
相關標籤/搜索