程序員老是在作重複性的工做,經常由於80%公用的內容,但有20%的不一樣之處,致使要重寫,或複製修改;程序員
更好的共用化封裝是程序員不斷追求的目標,設計的公用性與適用度還有效率之間要找平衡點;gulp
舉些例子,分享給新手!(示例來自個人 fixedstar 引擎)數組
輔助功能設計儘量不影響原函數設計,也支持原函數的變更升級;緩存
只關注輔助功能自己使用場景,不受業務應用場景限制;app
單一功能實現,不要多功能集成;函數
高頻觸發的行爲經常會產生沒必要要的消耗,能夠設計節流函數來控制使用頻率。工具
/** * 獲取在控制執行頻率的節流函數 * * - 與上一次執行時間超過等待時間時能夠直接觸發; * - 間隔時間以內調用不會觸發,保留最後一次調用定時在等待時間後觸發; * * 可用於高頻發事件的回調,如: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);
如一般作優化時,須要計算代碼運行時間,工具備時不能很細緻也受到環境限制,因此不可避免須要寫一些計時的腳本調試或統計: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);
一般獲取數據屬性都是靜態的,也就是 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);
未完待續...