什么是节流和防抖#
在某些特殊情况下,会出现高频操作。比如监听器在监听页面的滚动或者浏览器窗口尺寸变化时,页面滚动或者浏览器窗口变化时会使得监听器出现高频操作,这时候会非常消耗浏览器的性能,而且可能会出现错误。所以需要限制高频操作,防止出现一些不是预期的情况,或者节约性能。
防抖和节流就可以限制这些不必要的高频操作的方法。
防抖#
触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。
实现思路:每次触发事件时都取消之前的延时调用方法。
function debounce(fn, wait = 200) {
let timeoutId = null; // 维护一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeoutId); // 每次执行程序前,把上一个 setTimeout 清除掉,重新开始计时
timeoutId = setTimeout(() => {
fn.apply(this, arguments);
}, wait);
};
}其他要求:如果需要先立即执行一次呢?
实现思路:维护一个变量,先执行一次函数,然后再实现防抖。
const debounce = (fn, wait = 200) => {
let timeoutId, inDebounce;
return function (...args) {
if (!inDebounce) {
fn.apply(this, args); // 先执行一次
inDebounce = true; // 执行之后在打开防抖开关
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), wait);
}
};
};节流#
高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
实现思路:每次触发事件时都判断当前是否有等待执行的延时函数。
function throttle(fn, wait = 200) {
let canRun = true; // 维护一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
canRun = false; // 为True的话,立即设置为 false
setTimeout(() => {
fn.apply(this, arguments);
canRun = true; // 每次执行完程序后必须使 canRun 为 true,可以开始下次计时
}, wait);
};
}其他要求:如果需要先立即执行一次呢?
实现思路:同理,维护一个变量,先执行一次函数,然后再实现节流。
const throttle = (fn, wait = 200) => {
let inThrottle, lastFn, lastTime;
return function () {
const context = this,
args = arguments;
if (!inThrottle) {
fn.apply(context, args); // 先执行一次
lastTime = Date.now(); // 执行完成后开始计时
inThrottle = true; // 打开节流开关,开始节流操作
} else {
clearTimeout(lastFn); // 先清除上次的定时器
lastFn = setTimeout(function () {
// 时间到了再执行
if (Date.now() - lastTime >= wait) {
fn.apply(context, args);
lastTime = Date.now(); // 执行完成后重置时间
}
}, Math.max(wait - (Date.now() - lastTime), 0)); // 延迟时间:定时 - (现在时间 - 上次执行的时间) 或者 0
}
};
};使用场景#
-
防抖
- search 搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
-
节流
- 鼠标不断点击触发,单位时间内只触发一次。
- 监听滚动事件,比如是否滑到底部自动加载更多,可以用节流来判断。
注意事项#
在使用防抖和节流限制监听事件的高频操作时,在页面卸载时,还需要删除监听事件。
以 Vue 为例:
<script>
export default {
created() {
// 监听滚动
this.onScroll = window.addEventListener(
'scroll',
throttle(this.loadMore, 300)
)
},
beforeDestroy() {
// 删除监听器
if (this.onScroll) {
window.removeEventListener('scroll', this.loadMore)
}
},
methods: {
loadMore() {
// code
}
}
</script>把监听的方法提取出来,在监听和删除监听时都可以很好的使用。