是什么
防抖和节流本质上都是前端对过高频率执行的限制。
防抖 : 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
节流 : 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
理解:
防抖是公交车,等最后一个人上来之后再多等三分钟,每次有人上来就重新计时。
节流是地铁,五分钟内就只有这一班。
防抖代码实现
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
let timer;
function debounce(fn, delay) {
clearTimeout(timer);
timer = setTimeout(function(){
fn();
}, delay);
}
在上面的代码中,会出现一个问题,timer被放到了全局作用域中,我们应该用闭包将这个变量包裹在防抖函数内部。
function debounce(fn, delay) {
let timer; // 维护一个 timer
return function (...args) {
let _this = this; // 缓存this指向
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args);
}, delay);
};
}
节流代码实现
在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
function throttle(fn, delay) {
let timer;
return function (...args) {
let _this = this; // 缓存this指向
if (timer) return;
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null;
}, delay)
}
}
这个函数中 if (timer) return;
也是就是说如果当前存在一个计时器,后续的逻辑就不执行了。
或者用时间戳来实现:
function throttle(fn, delay) {
let previous = 0;
return function(...args) {
let now = new Date();
if(now - previous > delay) {
fn.apply(this, args);
previous = now;
}
}
}
这里其实这两种方案都符合预期,即在一个单位时间内,只能触发一次函数。 但在实际应用的场景下,我希望执行的最后一次是要触发的,例如类似百度的输入框。 我想要随着输入框变化进行实时搜索,假设我们需要在这里加一个节流,我肯定希望用我最后输入的结果去请求。 但是我们这种写法如果最后一次变更和倒数第二次间隔不够的话,最后一次是不会触发的。
所以我们对他做一个优化,将上面两种方式结合起来就能实现:
function throttle(fn, delay) {
let previous = 0;
let timer = null
return function() {
let _this = this; // 缓存this指向
let now = new Date();
if (new Date() - previous > delay) {
clearTimeout(timer);
timer = null;
previous = new Date();
fn.apply(_this, args)
} else {
timer = setTimeout(() => {
fn.apply(_this, args)
}, delay)
}
}
}