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函数之前,先对输入的内容进行判断,若其值为空、值不变不调用。
golang技术交流群:316397059,vuejs技术交流群:458915921 囤币一族:621258209,有兴趣的可以加入
微信公众号: 心禅道(xinchandao)投资论道