# JavaScript防抖与节流机制详解
# 1. 防抖机制
# 1.1 概念理解
防抖(Debounce)是一种限制函数执行频率的技术。可以理解为电梯的工作原理:当电梯准备关门时,如果有人进入,电梯会重新开始计时,直到等待时间结束才关门。在前端开发中,防抖常用于处理用户频繁触发的事件,如输入框搜索、窗口大小调整等。
# 1.2 工作原理
防抖的核心思想是:事件被触发后,函数不会立即执行,而是等待一段时间(延迟时间)后再执行。如果在这段时间内事件再次被触发,则重新计算等待时间。
# 1.3 防抖实现
# 1.3.1 基础版本
基础版本的防抖函数在事件触发后等待指定时间再执行函数,如果在等待期间再次触发事件,则重新计时:
function debounce (func, wait) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
// eslint-disable-next-line prefer-rest-params
func.apply(this, arguments);
}, wait);
};
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 1.3.2 立即执行版本
立即执行版本在事件触发后立即执行函数,然后在指定时间内不再触发事件才能继续执行:
function debounce (func, wait) {
let timeout;
return function () {
clearTimeout(timeout);
// eslint-disable-next-line prefer-rest-params
if (!timeout) func.apply(this, arguments);
timeout = setTimeout(() => {
timeout = null;
}, wait);
};
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 1.3.3 完整版本
完整版本支持选择立即执行或延迟执行模式:
/**
* @desc 函数防抖
* @param func 函数
* @param wait 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
function debounce (func, wait, immediate) {
let timeout;
return function () {
clearTimeout(timeout);
if (immediate) {
// eslint-disable-next-line prefer-rest-params
if (!timeout) func.apply(this, arguments);
timeout = setTimeout(() => {
timeout = null;
}, wait);
} else {
timeout = setTimeout(() => {
// eslint-disable-next-line prefer-rest-params
func.apply(this, arguments);
}, wait);
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1.4 应用场景
防抖技术适用于以下场景:
- 搜索框输入:用户在搜索框中输入时,不需要每次按键都发送请求,只需在用户停止输入后再发送请求。
- 表单验证:在用户输入手机号、邮箱等信息时,不需要每次输入都进行验证,可以在用户停止输入后再进行验证。
- 窗口大小调整:在用户调整窗口大小时,不需要每次调整都重新计算布局,只需在用户停止调整后再计算。
# 2. 节流机制
# 2.1 概念理解
节流(Throttle)是一种限制函数执行频率的技术,确保函数在指定时间间隔内最多执行一次。可以将其想象成公交车的发车机制:无论有多少人在等车,公交车都会按照固定的时间间隔发车,不会因为等待的人多就频繁发车。在前端开发中,节流常用于处理高频触发的事件,如滚动条滚动、鼠标移动、页面缩放等,以避免函数执行过于频繁导致性能问题。
# 2.2 工作原理
节流的核心思想是:连续触发事件时,函数在指定时间间隔内只执行一次。节流会稀释函数的执行频率,确保函数不会被过于频繁地调用。
# 2.3 节流实现
# 2.3.1 时间戳版本
使用时间戳实现节流,当触发事件时,取出当前时间戳,减去之前的时间戳(初始值为0),如果差值大于设置的时间周期,则执行函数并更新时间戳:
function throttle (func, wait) {
let previous = 0;
return function () {
const now = Date.now();
if (now - previous > wait) {
// eslint-disable-next-line prefer-rest-params
func.apply(this, arguments);
previous = now;
}
};
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 2.3.2 定时器版本
使用定时器实现节流,在指定时间间隔后执行函数:
function throttle (func, wait) {
let timeout;
return function () {
if (!timeout) {
timeout = setTimeout(() => {
// eslint-disable-next-line prefer-rest-params
func.apply(this, arguments);
timeout = null;
}, wait);
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 2.3.3 完整版本
完整版本结合了时间戳和定时器两种方式的优点: 重点:
- 支持最简单的情况使用时间戳判断实现,最优性能
- 首次调用后,第二次执行设置定时器的时间需要计算差值
/**
* 基于定时器+时间戳实现(保证能够触发最后一次调用、第一次执行立即调用、超过 wait 时间后执行立即调用)
* @param {Function} func
* @param {boolean} wait
* @param {boolean} leading 是否第一次立即调用
* @param {boolean} trailing 是否保证末尾调用
*/
function throttle(func, wait, leading, trailing) {
let timer;
let lastTime = 0;
return function throttled(...args) {
if (timer) {
return;
}
const now = Date.now();
if (!leading) {
lastTime = now;
}
const remaining = wait - (now - lastTime);
const call = () => {
func.apply(this, args);
lastTime = now;
timer = null;
};
if (remaining <= 0) {
call();
} else if (trailing) {
timer = setTimeout(() => {
call();
}, remaining);
}
};
}
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
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
# 2.4 应用场景
节流技术适用于以下场景:
- 鼠标点击事件:在用户连续点击按钮时,限制单位时间内的触发次数,防止重复提交。
- 滚动事件监听:在用户滚动页面时,监听是否滑到底部以自动加载更多内容,使用节流来控制函数执行频率。
- 即时搜索联想:在用户输入搜索关键词时,使用节流来控制联想词的请求频率,避免过多请求。