前言
这几天看过一些面试题,发现大多都包含问了防抖和节流的实现,因为也是菜鸟所以记录一下学习到的实现方法,帮助自己以及后来的小伙伴理解。
防抖和节流用于在一些浏览器事件(如mouseover,scroll…)中不断调用其绑定方法,导致极大的资源浪费,所以用防抖和节流来对其调用加以限制。
防抖
防抖的作用是限制在一段时间内,仅对事件的最后一次或者第一次作出响应。
例如我给窗口添加一个滚动事件。
window.addEventListener("scroll", () => console.log("scroll"));
会看到控制台在滚动过程中不断调用其回调函数打印"scroll",而防抖的目的就是只对其最后一次触发该事件或第一次触发该事件作出响应。
首先实现一个简单版的防抖函数。
//用一个debounce函数封装
function debouce(fn, delay = 100) {
//缓存一个定时器
let timer = null;
//返回要实际执行的函数
return function(...args) {
//每次对这个函数的调用都会清空计时器
//作用是,只要在规定的延迟时间内,只要我又再次触发了这个函数,那么就视其为最后一个事件要调用的函数
clearTimeout(timer);
//经过delay的延时后执行所要执行的方法
timer = setTimeout(() => fn.apply(this, args), delay);
}
}
实际应用如下
function func(){
console.log("scroll");
}
window.addEventListener("scroll", debouce(func, 100));
下面接着实现一个可以立即执行的防抖函数
function debouce(fn, delay = 100, immediate) {
let timer = null;
return function(...args) {
clearTimeout(timer);
//主要区别在这:如果是需要立即执行的
//则声明doNow参数判断当前的timer是否为null,如果是null则表明距离上次的执行已经经过了delay时间
if (immediate) {
let doNow = !timer;
//这一行的意思就是,timer赋值一个有意义的值,而后经过delay延时将timer设为null,表示已经经过这么长时间
timer = setTimeout(() => timer = null, delay);
if (doNow) {
fn.apply(this, args);
}
} else {
timer = setTimeout(() => fn.apply(this, args), delay);
}
}
}
这样使用
window.addEventListener("scroll", debouce(() => console.log("scroll"), 100, true));
节流
节流就是判断该次事件触发的时间距离上次触发的时间是否大于delay延时,使用时间戳来实现,该实现是立即执行的
时间戳实现
function throttle(fn, delay = 100) {
let prev = Date.now();
return function(...args) {
let now = Date.now();
if (now - prev >= delay) {
fn.apply(this, args);
prev = Date.now();
}
}
}
定时器实现
function throttle(fn, delay = 100) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay);
}
}
}
题外话
我想试着对被防抖节流的函数进行带参的调用出现了点问题,我写了这样一个函数
function max(...args) {
let max = Math.max(...args);
console.log("result:" + max);
}
我对其进行这样的调用
window.addEventListener("scroll", debouce(max.bind(null, 3, 6, 11), 100));
而结果参数数组的最后一项竟然是个Event对象,不清楚为什么出现了这样的问题呢,最后只能在max函数内加上args.pop()
弹出最后一项。