“节流”和“防抖”都是用来提升用户体验,提升网站性能的手段,它们的技术手段都是“强制事件处理函数在特定的时间段内执行”。这样解释可能仍是不够直观。举两个例子吧:ajax
1: 比方说咱们给document绑定了一个scroll的事件,scroll事件是每滑动一个px,scroll的处理函数就会被调用执行,若是在你的处理函数里面恰巧作了一个很花时间或者很花空间的事情,比方说复杂的运算啊,ajax请求啊,那这样页面就可能出现卡顿的状况。app
2: 页面上有个地址的输入框,你但愿根据客户的输入内容,去帮客户补全。假如说这个地址列表须要经过ajax请求来获取,那咱们必定是但愿在客户中止输入了以后再去请求ajax而后来补全,而不是客户一边输入就一直请求ajax。函数
针对上面举例的状况,其实运用节流和防抖均可以作到,只是它们之间又有必定的区别:性能
防抖:防抖是每次想要执行这个函数,都得先等上一段时间。
节流:节流是在必定的时间段内,函数最多能够被调用多少次。也能够理解为函数以必定的频率被调用。网站
语言老是苍白显得,直接来看代码的实现吧。咱们先来实现一个防抖:this
//实现防抖函数 function debouncing(fn, waitTime){ let timer = undefined; return function(){ let context = this; let args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, waitTime) } } //scroll事件的处理函数 function scrollHandler(event){ console.log(new Date(event.timeStamp)); } //document的scroll事件上使用防抖函数 document.addEventListener('scroll', debouncing(scrollHandler,100), false);
实现防抖函数的核心就是每次事件被触发的时候,咱们不是当即去调用相应的handler,而是每一次都从新设置一个timeout,等待一段时间,而后再执行咱们的handler.code
2: 如今来尝试实现一个节流函数:事件
function throttling(fn, intervalTime){ let inInterval = false; return function(){ let context = this; let args = arguments; if(!inInterval) { fn.apply(context, args); inInterval = true; setTimeout(function(){ inInterval = false; }, intervalTime) } } } function scrollHandler(event){ console.log(new Date(event.timeStamp)); } document.addEventListener('scroll', throttling(scrollHandler,500), false);
节流的核心是管理一个布尔值开关变量(inInterval),以必定的频率切换它的true值和false值,事件处理函数只在这个开关变量值为某个特意值的时候才执行,以此来实现事件处理函数以必定的频率被调用。ci
节流函数它的实现有不少种,多种就在于控制这个开关变量的值的条件,会不同。在上面的例子里,我经过setTimeout的方式,间断性的来改变inInterval的值。回调函数
如今来详细分析一下上面的实现:
1: 第一次scroll事件触发的时候,scrollHandler被当即执行。这个是我我的的一个考虑,但愿对于第一次的事件触发能立刻有一个回馈给客户。
2: 当第一次回调函数执行完了以后,咱们立刻把'inInterval=true'
, 假如这时候第二次scroll触发,代码执行到 if(!inInterval)
,此时条件表达式的值为false, 因此scrollHandler不会被当即执行。在这以后的第N次scroll事件触发的时候,inInterval都有可能仍是是true,那么回调函数会一直不被执行。
3: 咱们以前在把inInterval设置为true以后,同时设置了一个timeout,在通过必定的时间(intervalTime)以后,inInterval会被设置为false; 假如在这以后立刻又触发了一次scroll事件,代码走到if(!inInterval)
,条件为true,scrollHandler就能够被执行了。
这一切看起来是这么完美地自圆其说,可是上面的代码存在一个问题,咱们没有考虑到一个极端状况:假如咱们最后一次的scroll事件,正好发生在这个循环时间内,那它就永远得不到执行了。这个可能会是一个隐藏的bug, 比方说你在进行一次拖拽事件,那目标元素可能永远都拖不到目的地。
因此咱们要改一下代码,让最后一次事件的回调函数老是能被执行:
function throttling(fn, intervalTime){ let inInterval = false; let lastTimer = undefined; let timer = undefined; return function(){ let context = this; let args = arguments; if(!inInterval) { clearTimeout(lastTimer); //这行代码很重要 fn.apply(context, args); inInterval = true; timer = setTimeout(function(){ inInterval = false; }, intervalTime) }else{ clearTimeout(lastTimer); lastTimer = setTimeout(function(){ fn.apply(fn, args); inInterval = false; }, intervalTime); } } } function scrollHandler(event){ console.log(new Date(event.timeStamp)); } document.addEventListener('click', throttling(scrollHandler,1000), false);
上面的代码,要特别解释一下这一行代码:clearTimeout(lastTimer); //这行代码很重要
假如咱们如今处理一个点击事件,若是咱们不加这行代码的话,会出现先点击的click事件反然后执行的问题。好比;咱们的intervalTime设置为10s, 而后咱们分别在第0s, 第5秒,第12秒都进行一次点击,咱们经过console.log(new Date(event.timeStamp))
打印每一次事件发生时的时间, 咱们会看到第5秒的那个click事件会比第12秒的那个click后输出,这就说明这里有问题。
因此咱们要在if(!inInterval){}里面把lastTimer给清掉,也就是经过clearTimeout(lastTimer);
这行代码。