出处:掘金
原作者:金泽宸
为什么要掌握防抖与节流?
它们是前端性能优化的“老三样”之一,常用于:
- 防止频繁触发:输入框搜索、窗口 resize、滚动监听
- 面试必问场景题:你怎么优化滚动事件监听?
概念区别一张图看懂
特性 | 防抖(debounce) | 节流(throttle) |
---|---|---|
执行时机 | 停止一段时间后执行一次 | 固定时间间隔内最多执行一次 |
场景 | 搜索框输入、按钮防重复提交 | 页面滚动、resize、mousemove |
函数防抖(debounce)
在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时
/**
* @description: 函数防抖:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
* @param {function} func 需要防抖的函数
* @param {number=500} delay 延迟时间(单位为毫秒),默认 500
* @return {function}
*/
function debounce(func, delay = 500) {
let timer = null
return function(...args) {
timer && clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
timer = null
}, delay)
}
}
使用示例:
window.addEventListener('resize', debounce(() => {
console.log('窗口变化');
}, 500));
函数节流(throttle)
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
时间戳
/**
* @description: 函数节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
* @param {function} func 需要节流的函数
* @param {number=500} wait 等待时间(单位为毫秒),默认 500
* @return {function}
*/
function throttle(func, wait = 500) {
let last = 0
return function(...args) {
const now = Date.now()
if (now - last < wait) return
last = now
func.apply(this, args)
}
}
- 第一次立即执行
- 无法保证最后一次是否执行
定时器
/**
* @description: 函数节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
* @param {function} func 需要节流的函数
* @param {number=500} wait 等待时间(单位为毫秒),默认 500
* @return {function}
*/
function throttle(func, wait = 500) {
let timer = null
return function(...args) {
if (timer) return
timer = setTimeout(() => {
func.apply(this, args)
timer = null
}, wait)
}
}
- 第一次延迟执行
- 最后一次延迟执行
时间戳 + 定时器
/**
* @description: 函数节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
* @param {function} func 需要节流的函数
* @param {number=500} wait 等待时间(单位为毫秒),默认 500
* @return {function}
*/
function throttle(func, wait = 500) {
let timer = null
let start = 0
return function(...args) {
let now = Date.now()
let remainning = wait - (now - start) // 剩余时间
timer && clearTimeout(timer)
if (remainning <= 0) { // 没有剩余时间,需要立即执行
func.apply(this, args)
start = Date.now()
} else { // 有剩余时间,剩余时间结束后执行
timer = setTimeout(() => {
func.apply(this, args)
timer = null
}, remainning)
}
}
}
- 第一次立即执行
- 最后一次延迟执行
使用场景
函数防抖
input
、resize
等连续的事件,只需触发一次回调:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小
resize
。只需窗口调整完成后,计算窗口大小。防止重复渲染
函数节流
scroll
等间隔一段时间执行一次回调:
- 滚动加载,加载更多或滚到底部监听
- 高频点击提交,表单重复提交
题目
常见面试陷阱
问题 | 答案 |
---|---|
防抖能否立即执行? | 可以,加 immediate 参数 |
节流 trailing 和 leading 如何实现? | 控制时间戳/定时器配合 |
节流和防抖能否合并? | 理论上不建议,但可以写工具库支持模式切换 |
this 和 arguments 会丢失吗? | 是的,需用 apply(this, args) 绑定上下文 |
延伸思考(面试加分)
lodash.debounce
和lodash.throttle
内部实现有何异同?- 如何封装一个 Vue/React 通用指令或 Hook 实现?