Fork me on GitHub

事件节流与防抖

js中的很多事件,如scroll事件、resize事件、鼠标事件(mousemove、mouseover等)、
键盘事件(keyup、keydowm等)都存在被频繁触发的问题
频繁触发回调导致大量计算会引发页面抖动甚至卡顿,因此我们必须用一些手段来控制事件被触发的频率–
事件节流(throttle)和事件防抖(debounce)

节流与防抖

这两个东西都以闭包的形式存在,通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用setTimeout来控制事件的触发频率

Throttle:第一次为准

核心思想:在某段事件内,不管触发多少次回调,都以这段时间内的第一次为准,即在一段时间内无视后来产生的回调请求
实际交互:用户触发了一次srcoll事件,我们就为这个触发操作开启计时器,直到这段时间到了,第一次触发的scroll事件对应的回调才会执行,这段时间其他scroll的回调都会被无视

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle (fn, interval) {
let lastTime = 0;
return function () {
// 保存调用时的上下文
const context = this;
// 保存调用时的参数
const args = arguments;
const nowTime = new Date().getTime();
if (nowTime - lastTime >= interval) {
lastTime = nowTime;
fn.apply(context, args);
}
}
}

debounce: 最后一次为准

核心思想:某段时间内,不管触发多少次回调,都只认最后一次
实际交互:用户每次触发scroll事件都会清除当前计时器,重新计时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function debounce(fn, delay) {
// 定时器
let timer = null;
return function () {
// 保存调用时的上下文
const context = this;
// 保存调用时的参数
const args = arguments;
// 每次事件被触发都去除之前的旧定时器
if (timer) {
clearTimeout(timer);
}
// 设定新的计时器
timer = setTimeout(function() {
fn.apply(context, args);
}, delay)
}
}

用throttle来优化debounce

想象一下,如果用户操作十分频繁–每次都不等debounce设置的delay就进行下一次触发
每次都重新生成定时器,回调函数一直被延迟,用户同样会觉得页面卡死

因此debounce需要一个“底线”,我们可以结合throttle的思想,设定一个原则:
delay时间内,重复触发可以重新生成定时器,delay时间到了,必须要给用户一个相应

这种结合版的思路,已经应用到很多成熟的前端库,作为加强版throttle函数的实现:

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
function upThrottle(fn, delay) {
let timer = null,
lastTime = 0;
return function () {
// 保存调用时的上下文
const context = this;
// 保存调用时的参数
const args = arguments;
// 记录本次触发的时间
const nowTime = new Date().getTime();

// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (nowTime - lastTime < delay) {
// 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
clearTimeout(timer);
timer = setTimeout(function() {
lastTime = nowTime;
fn.apply(context, args);
}, delay);
} else {
// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,立刻反馈给用户一次响应
lastTime = nowTime;
fn.apply(context, args);
}
}
}

应用

优化图片懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const imgs = document.querySelectorAll('.pic');
let num = 0;
function isInSight(el) {
const bound = el.getBoundingClientRect();
const innerHeight = document.documentElement.clientHeight || window.innerHeight;
return bound.top <= innerHeight + 100;
}
function lazyload() {
// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
for (var i = num; i < imgs.length; i++) {
// 元素相对于文档顶部的距离 - 滚动高度 < 可视窗高度
if (isInSight(imgs[i])) {
imgs[i].src = imgs[i].dataset['src'];
// 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
num = i + 1;
}
}
}

const scroll_throttle = upThrottle(lazyload, 1000);

// 监听Scroll事件
window.addEventListener('load', lazyload, false);
window.addEventListener('scroll', scroll_throttle, false);

效果展示


点击查看实现效果

本文标题:事件节流与防抖

文章作者:tongtong

发布时间:2018年04月12日 - 10:04

最后更新:2019年03月14日 - 20:03

原始链接:https://ilove-coding.github.io/2018/04/12/事件节流与防抖/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------本文结束-------------