JS中的函数节流throttle详解和优化

JS中的函数节流throttle详解和优化
在前端开发中,有时会为页面绑定resize事件,或者为一个页面元素绑定拖拽事件(mousemove),这种事件有一个特点,在一个正常的操作中,有可能在一个短的时间内触发非常多次事件绑定程序。
DOM操作时很消耗性能的,如果你为这些事件绑定一些操作DOM节点的操作的话,那就会引发大量的计算,在用户看来,页面可能就一时间没有响应,这个页面一下子变卡了变慢了。在IE下,如果你绑定的resize事件进行较多DOM操作可能直接就崩溃了。

怎么解决?函数节流(throttle)就是一种办法。

函数节流的原理
某些代码不可以在没有间断的情况连续重复执行。
第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。
当第二次调用该函数时,它会清除前一次的定时器并设置另一个。
如果前一个定时器已经执行过了,这个操作就没有任何意义。
然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。
目的是只有在执行函数的请求停止了一段时间之后才执行。

简单点说就是:当触发一个事件时,先setTimout让这个事件延迟一会再执行,如果在这个时间间隔内又触发了事件,那我们就clear掉原来的定时器,再setTimeout一个新的定时器延迟一会执行。
实际使用过程中考虑到用户体验问题,会在这个基础上做一些优化。

一个简单实现的例子:
函数实现方式:

function throttle(method, context) {
clearTimeout(methor.tId);
method.tId = setTimeout(function(){
method.call(context);
}, 100);
}

调用方法:

window.onresize = function(){
throttle(myFunc);
}

---------------

闭包实现方式:

var throttle = function(fn, delay){
var timer = null;
return function(){
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(context, args);
}, delay);
};
};

调用方法:

window.onresize = throttle(myFunc, 50);
-------------
上面的函数节流有个问题是:只要你在50ms内不间断resize,myFunc就一次都不执行处理函数。
对上面的节流函数做拓展:

var throttleV2 = function(fn, delay, mustRunDelay){
var timer = null;
var t_start;
return function(){
var context = this, args = arguments, t_curr = +new Date();
clearTimeout(timer);
if(!t_start){
t_start = t_curr;
}
if(t_curr - t_start >= mustRunDelay){
fn.apply(context, args);
t_start = t_curr;
}
else {
timer = setTimeout(function(){
fn.apply(context, args);
}, delay);
}
};
};

拓展后的节流函数升级版,可以设置第三个参数,即必须触发执行的时间间隔。

方法调用:
window.onresize = throttleV2(myFunc, 50, 100);

50ms的间隔内连续触发的调用,后一个调用会把前一个调用的等待处理掉,但每隔100ms至少执行一次。
一开始记录第一次调用的时间戳,然后每次调用函数都去拿最新的时间跟记录时间比,超出给定的时间就执行一次,更新记录时间。

-----------------
例子场景2:实现常见的搜索功能
1.没有使用函数节流的情况下,为input绑定keyup事件处理函数,在控制台输出我输入的内容。
每按下一个键盘键,就输出了一次。短短的一些内容,输出了15次,如果每一次都是一次ajax查询请求的话就发了14个请求了。在性能上的消耗可想而知。
假如我不断地输入,输入了很多内容,但是我每两次之间的输入间隔都小于自己设置的delay值,那么,这个queryData搜索函数就一直得不到调用。实际上,我们更希望的是,当达到某个时间值时,一定要执行一次这个搜索函数。所以,就有了函数节流的改进模式。

函数节流增强版
HTML:
<input id="search" type="text" name="search">
JS:

    <script>
function queryData(text){
console.log("搜索:" + text);
}
var input = document.getElementById("search");
input.addEventListener("keyup", function(event){
throttle(queryData, null, 500, this.value,1000);
// throttle(queryData, null, 500, this.value);
// queryData(this.value);
}); function throttle(fn,context,delay,text,mustApplyTime){
clearTimeout(fn.timer);
fn._cur=Date.now(); //记录当前时间 if(!fn._start){ //若该函数是第一次调用,则直接设置_start,即开始时间,为_cur,即此刻的时间
fn._start=fn._cur;
}
if(fn._cur-fn._start>mustApplyTime){
//当前时间与上一次函数被执行的时间作差,与mustApplyTime比较,若大于,则必须执行一次函数,若小于,则重新设置计时器
fn.call(context,text);
fn._start=fn._cur;
}else{
fn.timer=setTimeout(function(){
fn.call(context,text);
},delay);
}
}
</script>

显然,连续的输入,到一定时间间隔之后,queryData函数必然会被调用,但是又不是频繁的调用。达到了节流的目的,又不会影响用户体验。

进一步的优化可以在调用throttle函数之前,先对输入的内容进行判断,若其值为空、值不变不调用。

上一篇:【Android】Handler、Looper源码分析


下一篇:Calculating a bearing between points in location-aware apps