防抖与节流
函数节流
函数节流就是在指定时间间隔内只执行一次该函数
举个栗子:滚动条滚动事件
window.addEventListener(
"scroll",
function() {
console.log("页面滚动了");
},
false
);
该代码的初衷是:用户每滑动一次滚动条,执行一次console.log('页面滚动了')
,但事实是:
用户只滑动了一次滚动条,就多次触发了scroll
事件,因为浏览器每隔一个很短的时间间隔就要判断滚动条的变化,如果执行函数内需要执行 DOM 操作,将会非常消耗资源,这个时候节流函数就派上用场了:
window.addEventListener("scroll", func(putOut, 400), false);
function func(method, waitTime) {
let run = true;
return function() {
if (!run) return;
run = false;
setTimeout(() => {
method.apply(this, arguments);
run = true;
}, waitTime);
};
}
function putOut() {
console.log("页面滚动了");
}
putOut
函数是我们触发scroll
事件希望执行的函数,而func
是实际执行的函数,用来控制putOut
函数执行次数,在func
中声明了一个Boolean
类型的变量run
,然后返回一个函数,先判断!run
值是否为true
(run 是否为 false),条件成立则直接return
退出函数,否则执行setTimeout
,也就是在waitTime
毫秒之后执行延时器内的命令,直到waitTime
毫秒之后,才会再次执行函数
在这里
putOut
是可以直接被调用的,但是在实际情况中往往不会像现在一样只有一个console
语句,可能会有很多的 DOM 操作,也可能会涉及到this
指向问题,为了保证函数this
指向正确,采用 apply 的方式执行函数putOut
效果非常明显,由于我们设置了节流函数,所以执行函数的次数大大减少,效率大大提高
防抖函数
防抖函数是指行为被触发的间隔超过指定间隔的时候,才会执行该函数
一个很经典的例子就是输入框的监听:
在用户注册的时候,可能需要检测用户的用户名是否重复,有两种方案:
- 输入框失去焦点的时候触发检测事件
- 输入框内容改变的时候触发检测事件
很多网站都会选择第二种方案,因为第一种的用户体验不太好,每次都要点一下其他地方才能知道自己的用户名有没有重复
let input = document.getElementById("input");
input.addEventListener("input", function() {
console.log(this.value);
});
这样就实现了第二种方案(AJAX 用 console.log 代替),效果是有了,但是我们想要的是用户输入完整个用户名之后在开始检测,而事实是每当用户输入的时候就会进行检测,这样是相当浪费资源的行为,如果我把console.log
替换成AJAX
请求,那么我在输入完123456789
的时候已经做了 9 次AJAX
请求,这个时候防抖函数就派上用场了
let input = document.getElementById("input");
input.addEventListener("input", func(putOut, 400));
function func(method, waitTime) {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
method.apply(this, arguments);
}, waitTime);
};
}
function putOut() {
console.log(this.value);
}
其实和函数节流十分相似,用户触发input
事件之后执行func
函数,函数内声明了一个变量timer
,接着返回一个闭包函数,因为用户在waitTime
毫秒之内又触发了input
事件,所以清除先前的延时器,重新开始计时
这就实现了函数防抖功能,在用户输入完成之后waitTime
毫秒之内不再有任何字符输入,就会执行putOut
函数
总结
函数节流和函数防抖对提升性能都起到了非常大的作用,都用到了延时器,并且都用到了闭包函数来获取函数func
内声明的属性的状态,以前耿直的我一直选择触发行为直接执行方法的方式,现在才知道是多么耗费资源