程序员老是在作重复性的工做,经常由于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);
未完待续...