对于高频率的事件触发,为了优化页面性能,我们一般会对其做函数节流。比如: resize、keydow、scroll事件等。用户的频繁操作,会导致事件高频率的执行,这样会出现页面抖动啊、频繁调接口啊等问题。为了优化,我们采用函数节流,原理就是利用setTimeout控制触发回掉的频率。
1.第一种方案:
var timer; function throttle1(fun,sec){ clearTimeout(timer); timer = setTimeout(function(){ fun(); },sec); } $(function(){ $(window).scroll(function(){ throttle1(function(){ console.log('我滚!!!'); },500); }); });
第一种方案最简单,原理一看就懂,用setTimeout的目的是在用户触发之后到500毫秒之内不会执行回调,500毫秒才会执行回调一次,对于之前的事件触发是没有执行回调的。但缺陷是无法获取回调的传入参数,即使可以获取,也不能保证执行上下文this的指向。
2.第二种方案:
function throttle2(fun,sec){ var timer = null; return function(){ clearTimeout(timer); var context = this,arg = arguments; timer = setTimeout(function(){ fun.apply(context,arg); },sec); timeer = null; } } $(function(){ $(window).scroll( throttle2(function(){ console.log('我滚!!!'); },500) ); });
第二种方案能保证回调函数的执行上下文,支持回调的多参数传入。
但是大家都知道setTimeout是一个异步函数,会被挂起,放到异步队列的最后。当主线程执行完之后,才会执行异步队列里的回调。在用户不停的操作:如不停的滚动滚动条、改变窗口大小的时候,setTimeout是会被挂起,只有在用户操作完,才会执行setTimeout的回调。那么问题来了,用户一直操作会影响正常功能使用啊!那第三种方案就是为了解决setTimeout被挂起之后,回调函数不执行的问题。
3.第三种方案:
function throttle3(fun,sec,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){ fun.apply(context, args); t_start = t_curr; }else { timer = setTimeout(function(){ fun.apply(context, args); }, sec); } } } $(function(){ $(window).scroll( throttle3(function(){ console.log('我滚!!!'); },500,1000) ); })
利用第三种方案,就完美的解决了setTimeout被挂起的情况,保证用户的正常操作效果。