javascript防抖与节流
前言
不知道小伙伴在前端工作中是否遇到以下问题:
1.如果实现了dom拖拽功能,每当元素稍微移动一点便触发了大量的事件回调函数,甚至可能导致浏览器直接卡死,这时候怎么办?
2.如果给一个按钮绑定了表单提交的post事件,但是用户有些时候在网络情况极差的情况下多次点击按钮造成表单重复提交,如何防止多次提交的发生?
概念
函数防抖(debounce)
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
<font color=red>简而言之:对于一定时间段的连续的函数调用,只让其执行一次。</font>
应用场景:
- 每次 resize/scroll 触发统计事件
- 文本输入(input)的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
函数节流(throttle)
规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效
<font color=red>简而言之:让一个函数不要执行得太频繁,减少一些过快的调用来节流。</font>
应用场景:
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
实现方式
防抖debounce(实现方式有多种,理清逻辑即可):
function debounce(fn,delay) {
var timer = null;
return function (e) {
var context = this;
var args = arguments;
if(timer){
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
}
// 处理函数
function handle() {
console.log('防抖:', Math.random());
}
//滚动事件,如此一来,如果用户频繁拖到滚动条,最终将只会执行一次事件回调
window.addEventListener('scroll', debounce(handle,500));
节流throttle(实现方式有多种,理清逻辑即可):
function throttle(fn, delay) {
var previous = 0;
var timer = null;
return function () {
var context = this;
var args = arguments;
if (!previous) {
previous = Date.now();
fn.apply(context, args);
} else if (previous + delay >= Date.now()) {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function () {
previous = Date.now();
fn.apply(context, args);
}, delay)
}
else {
previous = Date.now();
fn.apply(context, args);
}
}
}
// 处理函数
function sayHi(e) {
console.log('节流:', e.target.innerWidth, e.target.innerHeight);
}
//窗口缩放事件
window.addEventListener('resize', throttle(sayHi,500));
总结
函数防抖:
- 将多次操作合并为一次操作进行。
- 原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流:
- 使得一定时间内只触发一次函数。
- 原理是通过判断是否有延迟调用函数未执行。
两者区别:
- 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。
- 函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。