写在前面
9月23日昨天请假一天
上午有学术讨论,对于我这个学术垃圾而言,确实是煎熬,但是马上也要面临着开题,这也是早晚都要跨过去的一步!
下午去新校区办了点儿事,晚上参加了华为的笔试。嗯~只能说自己太菜了。。。
OK,回到正题。今天要说的就是防抖和节流
问题描述
防抖和节流大家都不陌生,只要涉及某些事件频繁触发的场景,就会有他们的存在。
想象一个场景,假如你要去登录一个网站,就像下面这样
假设这个网站并没有去做一些其他的处理,那么当你点击 login 按钮之后,虽然网站后台会进行一系列的操作,但是你看到的前端页面是没有任何反应的。
你觉得是不是刚才没有点上,于是你又点了几次,然后网站后台又重新做了几次同样的操作。这样会浪费很多后台资源。
其他一些典型场景包括浏览器的 resize() 事件、滚动条事件等
于是你需要一些方法能够控制函数执行的频率,这就是防抖和节流要解决的问题
防抖
防抖:将函数的多次执行变为最后一次执行
**目标:**当用户多次点击 login 按钮,我们仅让用户的最后一次点击生效
还是以上登录的场景,假设我们有以下代码:
function login () {
// do something...
console.log('登录成功');
};
const loginBtn = document.querySelector("#login");
loginBtn.addEventListener('click', login);
以上代码的意思是获取到 login 按钮,并绑定 login() 函数,其中 login() 函数省略其它步骤,只保留打印登录成功的信息
第一步我们定义 debounce() 防抖函数,将 login() 作为参数传入
function debounce(fn) {
// do Something...
}
loginBtn.addEventListener('click', debounce(login));
既然是让最后一次生效,那么就需要对之前的点击做一下延时操作,想到延时,第一反应想到的就是 setTimeout 函数,于是我们的函数雏形为:
function debounce (fn) {
setTimeout(function () {
fn();
}, 1000);
}
这样能做到让传入的函数在 1s 后执行,但是这么做的问题就是这个函数会在 1s 后立即执行
所以我们应该在 debounce() 函数内返回一个函数,这样就能做到用户点击后函数才执行
function debounce (fn) {
return function () {
setTimeout(function () {
fn();
}, 2000);
}
}
我们改正了函数立即执行的问题,但是我们目前实现的这个函数还是只能将执行时间延迟,并不能让函数只响应用户最后一次点击
我们现在需要做的是限制计时器,这里我们利用到的是 setTimeout 函数的返回值。当用户多次点击时,如果返回值不为空(函数还未执行),就清除计时器。这样就能保证函数只响应用户的最后一次操作
function debounce (fn) {
let timer = 0;
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(function () {
fn();
}, 2000);
}
}
以上代码就能基本满足我们上面的问题,但是真实的场景中,还涉及到函数的参数和函数内部的 this 指向。
要解决这个问题最关键的是要想明白,(以上场景中 addEventListener)真正执行的函数是 debounce 内部 return 后的匿名函数
不管是传参还是 this 的绑定都是针对 return 后的匿名函数
所以最终我们的代码是:
function debounce (fn) {
let timer = 0;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, 1000);
}
}
那么,你知道为什么 setTimeout 里面的函数要用箭头函数的形式吗?
还有,在以上用户多次点击登录的场景中,如果让用户多次点击的最后一次生效对用户而言是很不好的体验,那能不能让用户第一次点击就生效,让第一次以后的多次连续点击失效呢?
以下是仅让第一次点击生效的代码:
function debounce (fn) {
let timer = 0;
return function (...args) {
if (timer) clearTimeout(timer);
let firstClick = !timer;
if (firstClick) {
fn.apply(this, args);
}
timer = setTimeout(() => {
timer = 0;
}, 1000);
}
}
以上的代码你看懂了吗?
节流
节流:将多次操作变成每隔一段时间执行
还是以上的场景,我们想让用户连续多次点击,在固定时间间隔内执行一次。代码实现如下:
function throttle (fn, delay = 3000) {
let begin = 0;
return function (...args) {
let cur = new Date().getTime();
if (cur - begin > delay) {
fn.apply(this, args);
begin = cur;
}
}
}
这就是节流的代码实现,是不是很简单?