前言
在网页中提交表格时,常常有用户疯狂点击表单提交,导致重复发送的网络请求;在交互相关的动画中,有些场景通过mousemove监听,但是若每一秒钟都触发函数会降低网页性能。
以上两个举例正是防抖(debounce)与节流(throttle)的所对应场景解决的问题。
本文描述何为防抖(debounce)与节流(throttle),并如何用原生实现它们。当然,想要现成的函数在lodash中已经有了——lodash - debounce、lodash - throttle。
- 防抖(debounce):把多个连续重复的调用合并为一个,仅调用一次函数。
- 节流(throttle):节流会延迟函数执行,减少事件触发的响应。
可在此网站的可视化中了解它们的区别:点击这里
接下来描述防抖与节流的原生实现。原生实现学习于函数防抖与函数节流 - 知乎 - 司徒正美,在此基础上增添注释与理解。
防抖
防抖的原理是使用了闭包,通过不断地记录新的定时器与清除旧的定时器,达到函数调用不断延迟,且仅调用一次的效果。
function debounce(func, delay){
let timeout;//记录定时器
return function(e){
console.log("清除",timeout, e.target.value);
clearTimeout(timeout);
const context = this;
const args = arguments;
timeout = setTimeout(function(){
console.log("———");
func.apply(context, args);
},delay);
console.log("新的",timeout, e.target.value);
};
};
const validate = debounce(function(e){
console.log("验证函数触发!", e.target.value, new Date())
}, 300);
document.querySelector('input').addEventListener('input', validate);
节流
节流的原理也是使用了闭包,记录了开始时间start及每一次触发时间curr,两变量相减得到差值,当插值大于节流分片的时间时,可执行函数,同时更新start。另外通过setTImeout防止函数持续触发时间太短,没有调用函数,对于定时器也有所每次清除。
function throttle(fun, thresholdTime){
let timeout;
let start = new Date();
let threshold = thresholdTime || 160;
return function(){
const context = this;
const args = arguments;
const curr = new Date();
console.log("清除timeout")
clearTimeout(timeout);
if(curr - start >= threshold){//间隔时间到则可执行函数
console.log("间隔时间", curr-start);
fun.apply(context, args);
start = curr; //执行完毕,更新开始时间
}else{
//在方法在离开事件时也能执行一次,防止持续触发时间很短,导致方法无法触发。
timeout = setTimeout(function(){
fun.apply(context, args)
}, threshold);
}
}
}
const mousemove = throttle(function(e){
console.log("mousemove函数触发");
},300);
document.querySelector('#test').addEventListener('mousemove',mousemove);
总结
实现一遍防抖与节流的封装后并不难理解它们的思想,主要是利用了闭包保存创建时的环境的特性,记录变量已完成函数是否执行的判断。
参考资料
函数防抖与函数节流 - 知乎 - 司徒正美
lodash - debounce
lodash - throttle
Difference Between throttling and debouncing a function - StackOverflow
防抖节流可视化