js有些事件例如resize,mousemove等是会不间断触发的,如果这些事件的回调函数里有操作DOM、复杂算法或者Ajax向服务器发送请求等就会严重影响性能及其资源浪费。为了避免这个问题,我们一般会使用定时器来对函数进行节流。
函数节流的基本思想是设置一个定时器,在指定时间间隔内运行代码时清除上一次的定时器,并设置另一个定时器,直到函数请求停止并超过时间间隔才会执行。
在《JavaScript高级程序设计》中有专门应对此问题的函数节流
function throttle(method,context){
clearTimeout(method.tId);
method.tId=setTimeout(function(){
method.call(context)
},300)
}
window.onresize=throttle(resizehandler);
throttle函数接收两个参数,即要执行的函数及执行环境,如果执行环境未定义,默认则为windows。在这个函数中,会在第一次执行时为method定义一个定时器属性,在指定时间间隔(300)内再次执行时会清除上一次定义的定时器并创建新定时器让函数在300毫秒后执行。
再看看下面这个函数节流方案
function throttle(method,delay){
var timer=null;
return function(){
var context=this, args=arguments;
clearTimeout(timer);
timer=setTimeout(function(){
method.apply(context,args);
},delay);
}
}
window.onresize=throttle(resizehandler,500);
调用结果和第一种结果相同,都能有效的阻止函数重复调用,不同的是,第一种将定时器设置为函数的一个属性,而第二种方案通过闭包来实现。
以上两种方案存在一个问题,即如果事件一直触发,那么函数将永远不会被执行,这在某些时候并不符合我们的需求(例如像百度首页输入自动提示一样的东西,在text上绑定keyup事件,每次键盘弹起的时候自动提示,但是又不想提示那么频繁,于是用了上面方法,但是悲剧了,只有停止输入等500毫秒才会提示,在输入过程中根本就没有提示。只要是用户会盲打,在500毫秒内按一下键盘,提示函数就会不断被延迟,这样只有停下来的时候才会提示,这就没意义了),我们只是想在规定时间内减少函数执行次数,所以对以上函数做如下改进:
function throttle(method,delay,duration){
var timer=null, begin=+new Date();
return function(){
var context=this, args=arguments, current=+new Date();
clearTimeout(timer);
if(current-begin>=duration){
method.apply(context,args);
begin=current;
}else{
timer=setTimeout(function(){
method.apply(context,args);
},delay);
}
}
}
window.onresize=throttle(resizehandler,250,500);
当然如果只考虑IE9+,下面这个也可以试试:
function throttle(action, wait = 1000) {
let time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
action();
time = Date.now();
}
}
}
为了使(节流后的)滚动更平滑,你也可以通过使用IE10+的window.requestAnimationFrame()来实现函数节流:
function throttle(action) {
let isRunning = false;
return function() {
if (isRunning) return;
isRunning = true;
window.requestAnimationFrame(() => {
action();
isRunning = false;
});
}
}
当然,你可以通过现有的开源轮子来实现,就像Lodash或 _.throttle 。