防抖节流
-
防抖(debounce)
先来看看下面的代码:
//触发滚动事件,num 就加1 let num = 0; function incNum() { console.log('鼠标滚动中'); console.log(`${num++} ${Date().getSeconds()}s`); } window.addEventListener('scroll', incNum, false);
当滚动鼠标,
num
会疯狂加1,从下图中可以看到稍微滚动一下鼠标就输出了N个num
。-
什么是防抖?
防抖指的是让某些代码不可以在没有间断的情况下连续重复地执行。举个例子,一部电梯开门等待30s就会关门上升,在这30s的等待时间里,有人进来了,30s就重新从0开始计时,然后又有人进来了,30s就又重新计时,直到等待时间30s超时才会关门上升。
-
实现防抖
以上面的代码为例子来说明如何实现防抖:一直滚动鼠标,第一次触发
scroll
事件,在事件的执行函数里创建一个定时器,在指定的时间间隔后才运行相关代码。然后第二次触发scroll
事件,如果此时前面设置的定时器还没开始执行,就清除前一次的定时器并重新设置一个。这么做的目的就是让scroll
事件的执行函数在事件停止触发一段时间后才去执行,以此实现防抖。function debounce(func, delay) { let timeout; return function(){ // context 是为了绑定 this let context = this; // args 是为了能正常使用事件对象 event let args = arguments; if (timeout) clearTimeout(timeout); timeout = setTimeout(()=> { func.apply(context, args); }, delay); } } window.addEventListener('scroll', debounce(incNum, 1000), false);
从下面图片可以看出,防抖处理后,只有在鼠标停止滚动一段时间后,才会输出
num
和秒数。 -
取消执行
当滚动鼠标后,在1s的等待时间里,突然想取消运行
scroll
事件的函数了,该怎么办呢?答案是:在定时器没运行前,清除定时器。
function debounce(func, delay) { let timeout; let debounced = function(){ let context = this; let args = arguments; if (timeout) clearTimeout(timeout); timeout = setTimeout(()=> { func.apply(context, args); }, delay); } // 取消将要执行的定时器 debounced.cancel = function() { clearTimeout(timeout); timeout = null; console.log('已取消'); } return debounced; } let testFunc = debounce(incNum, 1000); window.addEventListener('scroll', testFunc, false); // 在页面的按钮上绑定取消函数 document.getElementById('cancel').addEventListener('click', testFunc.cancel, false);
-
立即执行
只要触发
scroll
事件,函数就立即执行,但必须要在函数执行完毕并过了一段时间后,再次滚动鼠标才会再次执行函数,而在等待时间里,如果滚动鼠标,等待时间会重新计时。function debounce(func, delay, immediate) { let timeout; let debounced = function(){ let context = this; let args = arguments; if (timeout) clearTimeout(timeout); // immediate 为 true 就表示立即执行,忽略或者为 false 即非立即执行 if (immediate) { let callNow = !timeout; timeout = setTimeout(()=> { timeout = null; }, delay); if (callNow) func.apply(context, args); } else { timeout = setTimeout(()=> { func.apply(context, args); }, delay); } } // 取消将要执行的定时器 debounced.cancel = function() { clearTimeout(timeout); timeout = null; console.log('已取消'); } return debounced; } window.addEventListener('scroll', debounce(incNum, 1000, true), false);
-
-
节流(throttle)
-
什么是节流?
还是电梯的例子:一部电梯开门等待30s就会关门上升,在这30s的等待时间里,有人进来了,计时继续累计,然后又有人进来了,计时依然累计,然后30s计时到了就立即关门上升(我还没上车呢.jpg)。
简单来说,就是连续触发事件,但在固定时间内只执行一次函数。
-
定时器实现
// 定时器版本,鼠标滚动1s后才执行函数 function throttle(func, delay){ let timeout; return function(){ let context = this; let args = arguments; if (!timeout) { timeout = setTimeout(()=> { func.apply(context, args); }, delay); } } } window.addEventListener('scroll', throttle(incNum, 1000), false);
疯狂滚动鼠标,但从下图可以看出函数只每秒执行一次。
-
时间戳实现
// 时间戳版本,鼠标滚动函数立即执行,间隔1s再执行下一次 function throttle(func, delay) { let previous = 0; return function() { let now = Date.now(); let args = arguments; if ( (now - previous) >= delay ) { func.apply(this, args); previous = now; } } } window.addEventListener('scroll', throttle(incNum, 1000), false);
-
合并实现与取消
将定时器和时间戳两个版本合并在一起,可以实现滚动鼠标,函数就立即执行,间隔1s再执行下一次,然后停止滚动鼠标后,还会多执行一次的效果。
function throttle(func, delay) { let previous = 0; let timeout; let now; let throttled = function() { now = Date.now(); let context = this; let args = arguments; if ( (now - previous) >= delay ) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); } else if (!timeout) { timeout = setTimeout(()=> { previous = now; timeout = null; func.apply(context, args); }, delay - (now - previous)); } } // 取消函数 throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = null; } return throttled; } let testFunc = throttle(incNum, 1000); window.addEventListener('scroll', testFunc, false); // 在页面的按钮上绑定取消函数 document.getElementById('cancel').addEventListener('click', testFunc.cancel, false);
-