0%

事件的节流和防抖

为什么要防抖和节流?

函数防抖的目的:**防止函数在极短的时间内反复调用,造成资源的浪费,造成性能问题**。

栗子1:

滚动条滚动、窗口尺寸变化、鼠标移动

如果我们需要监听这类事件,当它们发生变化时,哪怕只变化了一点点,都有可能造成成百上千次函数调用,这对网页性能的影响是极其巨大的。

  • 因此,如果我们只在每次变化结束时调用一次事件方法,就可以解决性能问题了,而区别”上次变化“和” 这次”变化,就需要指定一段时延,和就像电梯关门那样。

    栗子2

搜索的场景(input框)

以百度搜索框为例,需求是:每当输入一个文字(键盘按下事件),立即将已输入的值发送给远程服务器以请求返回对应的搜索结果,这样用户不用去点搜索按钮,可以提升用户体验。

但是

这样做同时也引发了一个性能问题:

比如我要搜索的是“JS防抖和节流”,如果我是一个字一个字敲进input框去的,那么浏览器就需要请求7次服务器,而实际上我只需要最后一次,而之前的6次请求发送的是不完整的信息,就不是实际我想搜的。这样就造成了资源的浪费

也就是说:防抖是为了减少这些无意义的请求,优化性能。

函数节流的目的:有时会为页面绑定 resize 事件,或者为一个页面元素绑定拖拽事件(其核心就是绑定 mousemove),这种事件有一个特点,就是用户不必特地捣乱,他在一个正常的操作中,都有可能在一个短的时间内触发非常多次事件绑定程序。

而DOM 操作时很消耗性能的,这个时候,如果你为这些事件绑定一些操作 DOM 节点的操作的话,那就会引发大量的计算,在用户看来,页面可能就一时间没有响应,这个页面一下子变卡了变慢了。甚至在 IE 下,如果你绑定的 resize 事件进行较多 DOM 操作,其高频率可能直接就使得浏览器崩溃

二者异同

方法相同:闭包+setTimeout

二者都是以闭包的形式处理,用setTimeout方法**控制事件触发频率**(将需要触发的函数作为定时器的回调函数)

目的相同:避免频繁调用函数,造成资源和性能问题

强调的都是为了避免频繁调用方法,为函数调用指定一个时间间隔

区别在于节流是在指定时间间隔内只认第一次(触发函数调用),而防抖只认最后一次

节流

概述:

只认第一次事件触发,在一段时间内无论触发多少次,都只执行第一次事件触发,并在计时结束后给出响应

逻辑:

图片

应用场景:

输入、搜索功能

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 函数节流
* 作用:一段时间内的多次操作,只按照第一次触发开始计算,并在计时结束时给予响应。
* 场景:如输入搜索功能
* @param fn 需要进行节流操作的事件函数
* @param interval 间隔时间
* @returns {Function}
*/
function throttle(fn, interval = 500) {
let last = 0;
return function (...args) {
let now = new Date();
if (now - last > interval) {
last = now;
fn.call(this, args);
}
};
}
/**
* 步骤
* 接受一个函数,和一个触发间隔时间,时间默认是 500ms
* 默认赋值为0
* 将多个参数解构为一个参数数组
* 记录本次触发回调的时间
* 判断上次触发的时间和本次之间的间隔是否大于我们设定的阈值
* 将本次触发的时间赋值给last,用于下次判断
* 使用call调用传入的回调函数,并传入参数
*
*/

使用:在 onScorll 中使用节流

1
2
3
4
5
6
// 使用 throttle 来包装 scorll 的回调函数,设置间隔时间为1s
const better_scorll = throttle(() => {
console.log("触发了滚动事件");
}, 1000);
document.addEventListenner("scorll", better_scorll);
// 1s内,无论触发多少次,都只从第一次触发之后的1s后给出响应。

防抖

概述:

只认最后一次事件触发,在一段时间内无论触发多少次,都只执行最后一次事件触发

逻辑:

  • 声明一个debounce函数
  • 传入要防抖的函数和指定延迟(设一个默认值),然后–

图片

应用场景:

登录、注册、支付

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 函数防抖
* 作用:一段时间内的多次操作,只执行最后一次。
* 场景:如点击登录/注册/支付等按钮时
* @param fn 需要进行防抖操作的事件函数
* @param delay 延迟时间
* @returns {Function}
*/
function debounce(fn, delay = 500) {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.call(this, args);
}, delay);
};
}
/**
* 接受一个函数,和一个触发间隔时间,时间默认是 500ms
* 定时器 id 默认赋值为null
* 将多个参数解构为一个参数数组
* 判断timer是否存在,如存在就取消定时器
* 然后创建一个新的定时器,并将id赋值给timer
* 然后如果再次点击重复上面的操作,一直到delay时间内没点时,定时器执行
* 执行时:使用call调用传入的回调函数,并传入参数
*
*/

使用:在 onScorll 中使用防抖

1
2
3
// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log("触发了滚动事件"), 1000);
document.addEventListener("scroll", better_scroll);

节流防抖一体化

为什么要一体化

用 Throttle 来优化 Debounce

  • 防抖的问题是:当用户操作频繁时,debounce将不断地销毁和重建定时器,用户迟迟得不到响应,会误以为“页面卡死”
  • 为此,我们需要借助Throttle的思想,打造一个“有底线”的debounce,等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器,但是只要 delay 时间一到,我就**必须给用户一个响应**

    逻辑

图片

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 加强版节流函数
* 作用:delay时间内的多次操作将会重新生成定时器,但只要delay时间一到就执行一次。
* 场景:如点击登录/注册/支付等按钮时
* @param fn 需要进行防抖操作的事件函数
* @param delay 延迟时间
* @returns {Function}
*/
function throttle(fn, delay = 500) {
let last = 0;
let timer = null;
return function (...args) {
let now = +new Date();
if (now - last < delay) {
// 如果间隔时间小于我们设定的阈值,则重新生成一个定时器
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
last = now;
fn.call(this, args);
}, delay);
} else {
// 如果 间隔时间大于设定的阈值,就执行一次。
last = now;
fn.call(this, args);
}
};
}
/**
* 接受一个函数和延迟时间,延迟时间默认是500ms
* 定义一个开始执行的时间戳和定时器id,赋予默认值
* 返回一个函数,并将参数转为数组。
* 函数内,拿到当前的时间戳
* 判断,是否小于间隔时间:
* 小于:则清楚定时器,然后重新生成定时器。定时器内直接赋值,然后call函数,
* 大于:直接赋值,然后call函数,
*/

使用:在 onScorll 中使用加强版 throttle

1
2
3
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log("触发了滚动事件"), 1000);
document.addEventListener("scroll", better_scroll);

参考:

事件的防抖和节流-九旬

彻底弄懂函数节流和函数防抖(看这一篇就够了)