程序员老是在做重复性的事情,经常由于80%公用的内容,但有20%的差别之处,致使要重写,或复制修正;
更好的共用化封装是程序员不停寻求的目的,设想的公用性与适费用另有效力之间要找平衡点;
举些例子,分享给新手!(示例来自我的 fixedstar 引擎)
1. 附加功用封包
辅佐功用设想尽量不影响原函数设想,也支撑原函数的更改晋级;
只关注辅佐功用自身运用场景,不受营业运用场景限定;
单一功用完成,不要多功用集成;
示例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是直接挪用,2、3、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:
如一般做优化时,须要盘算代码运转时候,东西偶然不能很仔细也遭到环境限定,所以不可防止须要写一些计时的剧本调试或统计:
/**
* 天生一个盘算函数运转时候的新函数(能够打印或运用回调函数返回运转时候值)
*
* 新函数与原函数挪用及功用稳定;
*
* @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);
更多例子
未完待续…