「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」
前言
节流防抖原理,前端面试十有八九都会问到这个问题,甚至有些直接让上来手写一个节流防抖函数,如果你平时只是浅浅了解,或者都是调用lodash的方法,这样肯定是不够的,今天我们就来手写一下这两个函数,理清其原理和实现思路。
防抖(debounce)
防抖--防止抖动,就是在事件触发的同时又有新的事件被创建或触发,设定一个时间 T,只有当事件触发 T 时间之后才会执行;在这个时间区间内,再次触发事件,只会重置这个时间 T,直到最后达到这个目标时间 T 才会真正执行。
- 常使用防抖的场景有:搜索联想、名称重复校验、resize、滚轮滚动、mousemove 等。
- 通常我们会使用 lodash 封装的 debounce 函数来实现防抖
那么如果是自己实现防抖应该怎么做呢?
首先,需要一个定时器来检验时间是否达到设定的阈值 T
setTimeout(() => {}, t)
当未达到预定时间再次触发事件,需要将之前的定时器清除,重新开一个定时器:
function debounce() {
let timer = null;
clearTimeout(timer);
timer = setTimeout(() => {
console.log("我执行了!");
}, 1000);
}
(其实不用判断是否达到了时间阈值,因为达到了内部的函数就执行了,没达到就还没执行,每次都执行一次清除就可了)
初版
时间和执行函数需要从外部传递进来
function debounce(func, t) {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(func, t);
};
}
存在问题: func 原本 this 的指向发生了变化 经过 debounce 之后,this 执行 window 所以我们需要手动处理一下 this 的指向问题
优化版
function debounce(func, t) {
let timer = null;
return function () {
const _this = this;
clearTimeout(timer);
timer = setTimeout(() => func.apply(_this), t);
};
}
此时 this 的指向已经正确了。
event 对象的处理
JavaScript 在事件处理函数中会提供事件对象 event,我们发现当前我们的封装丢掉了 event 对象。 所以,我们仍需要继续优化:
function debounce(func, t) {
let timer = null;
return function () {
const _this = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => func.apply(_this, args), t);
};
}
实现立即执行
已经实现了延迟执行,那么可不可以控制 debounce 先立即执行一次,其后再停止触发 T 时间后在执行呢?
我们增加一个immediate参数,判断是否要立即执行一次
function debounce(func, t, immediate = false) {
let timer = null;
return function () {
const _this = this;
const args = arguments;
if (immediate && !timer) {
func.apply(_this, args);
}
clearTimeout(timer);
timer = setTimeout(() => func.apply(_this, args), t);
};
}
取消
现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行啦,是不是很开心?
function debounce(func, t, immediate = false) {
let timer = null;
const debounce = function () {
const _this = this;
const args = arguments;
if (immediate && !timer) {
func.apply(_this, args);
}
clearTimeout(timer);
timer = setTimeout(() => func.apply(_this, args), t);
};
debounce.abort = () => {
clearTimeout(timer);
timer = null; //为了能立即执行
};
return debounce;
}
节流(throttle)
- 节流简单理解就是节约流量?可能比喻成水流比较合适些。
- 在设定时间内不管触发多少次,都只会执行一次。
- 也就是说未达到设定时间就阻断触发,直到超过时间阈值才会继续执行操作。
由此我们尝试下面的程序:
基础实现
function throttle(func, t) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
timer = null;
}, t);
func();
}
};
}
我们写个例子来测试它的效果:
let count = 1;
const container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = throttle(getUserAction, 1000);
立即执行
有了防抖函数的经验,我们很容易依葫芦画瓢写出节流的立即执行:
function throttle(func, t) {
let timer = null;
return function () {
if (!timer) {
+ func();
timer = setTimeout(() => {
timer = null;
}, t);
- func();
}
};
}
简单通过移动func执行的位置即可。
其实立即执行应该更合理,我们可以默认开启立即执行,