防抖和节流
概述
防抖和节流是前端开发中常用的函数优化手段,为了解决短时间内频繁触发某个功能函数而导致的性能问题。比如,触发频率过高而导致响应速度跟不上,以致出现延迟,假死或卡顿的现象。它们可以限制函数的执行频率,提升性能和用户体验。例如:用户的滚动、输入、点击和表单的重复提交等。
1. 防抖
1.1 是什么
当事件被触发 n 秒后,执行事件处理函数。若是在这 n 秒内,该事件又被触发,则重新计时,而不会执行事件处理函数。
它的原理是维护一个计时器,规定在 delay 时间之后触发回调,但是在 delay 时间内再次触发的话,就会取消之前的计时器而重新设置。
1.2 怎么用(如何实现)
const debounce = function (func, delay) {
let timerId = null;
return function() {
let context = this;
let args = arguments;
timerId && clearTimeout(timerId);
timerId = setTimeout(function() {
func.apply(context, args);
}, delay);
}
}
let container = document.getElementById('container');
container.addEventListener('scroll', debounce(containerScroll, 1500));
1.3 何时用
-
输入框搜索:当用户在搜索框中输入关键字时,使用防抖可以避免频繁发送搜索请求,而是在用户停止输入一段时间后才发送请求,减轻服务器压力。
-
表单输入验证:表单输入过程中,每次用户输入都可能触发验证操作。使用防抖函数可以延迟触发验证操作,只在用户输入完毕一段时间后进行验证,避免频繁的验证操作。
-
浏览器窗口调整事件:当用户调整浏览器窗口大小时,会触发 resize 事件。使用防抖函数可以延迟 resize 事件的触发,只在用户停止调整窗口一段时间后才执行对应的操作,避免频繁的计算和布局操作。
1.4 为什么 return 一个函数
- 闭包的使用:闭包允许函数记住并访问其所在的词法作用域,即使函数在其词法作用域之外执行。在防抖函数中,外部函数返回一个内部函数,这个内部函数可以访问外部函数的变量和参数。这样就可以让 timer 变量不被回收,下次执行时仍然指向的是上一次设置的定时器。
- 通过返回一个函数,你可以将这个防抖函数作为一个“工具”或“中间件”来使用,轻松地将它应用到任何需要防抖的函数上。你只需将需要防抖的函数作为参数传递给防抖函数,然后将返回的新函数用于事件监听或其他用途。
1.5 为什么要使用 apply
- 因为 func 执行的时候 this 指向全局对象(浏览器中是window),根据词法作用域,可以在外层用个变量保存下 this ,通过 apply 可以修正 this 指向以及传递正确的参数 arguments。
2. 节流
2.1 是什么
当持续性触发事件时,确保一定时间段内只会调用一次事件处理函数。其实现原理:是通过判断是否到达一定时间来触发回调函数。
2.2 如何实现
2.2.1 时间戳版
当触发事件的时候,获取当前的时间戳,然后减去之前的时间戳, 如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行
function throttle(func, wait) {
let context = null;
let args = null;
let previous = 0;
return function() {
context = this;
args = arguments;
let now = +new Date();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
特点:事件触发的时候立刻执行一次,停止触发后,不会再执行事件。
2.2.2 定时器版
当触发事件的时候,设置一个定时器, 后续再触发事件的时候, 判断定时器如果存在,就不执行, 直到上一个定时器执行后,才可以设置下个定时器
function throttle(func, wait) {
let context = null;
let args = null;
let timeout = null;
return function() {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args);
}, wait);
}
}
}
特点:事件触发的时候不会立刻执行,停止触发后,会再执行一次事件。
2.2.3 时间戳 + 定时器
function throttle(func, wait) {
let context = null;
let args = null;
let timeout = null;
let previous = 0;
let later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args);
};
let throttled = function() {
let now = +new Date();
// 下次触发 func 剩余的时间·
let remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
特点:事件触发的时候会立刻执行一次,停止触发的时候还会执行一次
2.3 何时用
- 页面滚动:当页面滚动时,使用节流可以限制滚动事件的触发频率,减少事件处理的次数,提高页面的响应性能。
- 拖拽场景: 在某些场景下,频繁触发位置变动会造成性能问题,固定时间内只执行一次,防止超高频次触发位置变动。
3.总结
- 函数防抖:当事件被触发 n 秒后,执行事件处理函数。若是在这 n 秒内,该事件又被触发,则重新计时,而不会执行事件处理函数;
- 函数节流:当持续性触发事件时,确保一定时间段内只会调用一次事件处理函数;
- 区别:函数节流不管事件触发有多频繁,都会保证在规定时间内执行一次事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数;所以如何选择需要根据具体的业务需求。