目录
[1. 输入框实时搜索(延迟执行模式)](#1. 输入框实时搜索(延迟执行模式))
[2. 按钮防重复点击(立即执行模式)](#2. 按钮防重复点击(立即执行模式))
[1. 时间戳方式(立即执行)](#1. 时间戳方式(立即执行))
[2. 定时器方式(延迟执行)](#2. 定时器方式(延迟执行))
[3. 结合时间戳和定时器(首尾均执行)](#3. 结合时间戳和定时器(首尾均执行))
[1. 滚动事件节流(时间戳方式)](#1. 滚动事件节流(时间戳方式))
[2. 按钮防重复点击(定时器方式)](#2. 按钮防重复点击(定时器方式))
[对比:防抖 vs 节流](#对比:防抖 vs 节流)
[一、.throttle() 源码解析](#一、.throttle() 源码解析)
[1. 核心逻辑](#1. 核心逻辑)
[2. 关键源码步骤](#2. 关键源码步骤)
[3. 核心特性](#3. 核心特性)
[二、.debounce() 源码解析](#二、.debounce() 源码解析)
[1. 核心逻辑](#1. 核心逻辑)
[2. 关键源码步骤](#2. 关键源码步骤)
[3. 核心特性](#3. 核心特性)
防抖(Debounce)
一、防抖的定义
防抖 是一种 优化高频触发事件 的技术,其核心思想是:在事件被频繁触发时,只有最后一次操作会被执行,中间的触发会被忽略。
-
典型场景:输入框实时搜索、窗口大小调整、滚动事件等需要限制执行频率的场景。
-
核心目标:减少不必要的计算或请求,提升性能和用户体验。
二、防抖的实现原理
防抖的底层实现依赖以下技术点:
-
定时器(
setTimeout
和clearTimeout
):用于控制事件触发的延迟时间。 -
闭包(Closure):保存定时器状态,确保多次触发时能共享同一个定时器。
-
函数包装:将原始函数包装成防抖函数,返回一个新函数供事件调用。
三、防抖的代码实现
以下是一个支持 立即执行 和 延迟执行 的通用防抖函数:
javascript
function debounce(func, wait, immediate = false) {
let timeout = null;
// 返回包装后的防抖函数
return function (...args) {
const context = this;
// 如果定时器存在,清除之前的定时器(取消未执行的延迟操作)
if (timeout) clearTimeout(timeout);
if (immediate) {
// 立即执行模式:首次触发立即执行,后续在 wait 时间内触发则重新计时
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null; // 恢复可执行状态
}, wait);
if (callNow) func.apply(context, args);
} else {
// 延迟执行模式:最后一次触发后等待 wait 时间再执行
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
}
};
}
四、代码解析
-
参数说明:
-
func
:需要防抖的原始函数。 -
wait
:防抖等待时间(单位:毫秒)。 -
immediate
:是否立即执行(true
表示首次触发立即执行,后续触发需等待)。
-
-
闭包保存状态:
timeout
变量通过闭包保存定时器 ID,确保多次触发共享同一状态。
-
apply
方法的作用:-
确保原始函数
func
的this
指向正确(指向触发事件的元素)。 -
传递事件参数(如
event
对象)。
-
-
两种模式的区别:
-
立即执行模式 :首次触发立即执行函数,之后在
wait
时间内再次触发会重新计时,直到停止触发超过wait
时间后,才能再次立即执行。 -
延迟执行模式 :每次触发都会重置计时,只有最后一次触发后等待
wait
时间才会执行。
-
五、使用示例
1. 输入框实时搜索(延迟执行模式)
html
<input type="text" id="searchInput" />
<script>
const searchInput = document.getElementById('searchInput');
// 原始搜索函数
function search(query) {
console.log('搜索关键词:', query);
}
// 防抖处理(延迟执行)
const debouncedSearch = debounce(search, 500);
// 绑定输入事件
searchInput.addEventListener('input', function (e) {
debouncedSearch(e.target.value);
});
</script>
2. 按钮防重复点击(立即执行模式)
html
<button id="submitBtn">提交</button>
<script>
const submitBtn = document.getElementById('submitBtn');
// 原始提交函数
function submitForm() {
console.log('表单已提交');
}
// 防抖处理(立即执行)
const debouncedSubmit = debounce(submitForm, 1000, true);
// 绑定点击事件
submitBtn.addEventListener('click', debouncedSubmit);
</script>
六、总结
-
防抖的核心逻辑:通过定时器和闭包控制高频事件的执行时机。
-
实现要点:
-
使用
setTimeout
和clearTimeout
管理延迟。 -
通过闭包保存定时器状态。
-
处理
this
指向和参数传递。
-
-
应用场景:
-
输入框实时搜索建议。
-
窗口大小调整后的布局计算。
-
防止按钮重复提交。
-
节流(Throttle)
一、节流的定义
节流 是一种 限制高频触发事件执行频率 的技术,其核心思想是:在事件被频繁触发时,固定时间间隔内只执行一次操作,忽略中间的触发。
-
典型场景:滚动事件、鼠标移动事件(如拖拽)、窗口大小调整、按钮频繁点击等。
-
核心目标:在保证功能正常的前提下,降低事件处理频率,优化性能。
二、节流的实现原理
节流的底层实现依赖以下技术点:
-
定时器(
setTimeout
和clearTimeout
)或时间戳:用于控制事件触发的间隔时间。 -
闭包(Closure):保存计时器状态,确保多次触发时能共享同一状态。
-
函数包装:将原始函数包装成节流函数,返回一个新函数供事件调用。
三、节流的代码实现
以下是两种常见的节流实现方式:
1. 时间戳方式(立即执行)
首次触发立即执行,之后在固定间隔内忽略后续触发。
javascript
function throttle(func, wait) {
let previous = 0; // 上次执行时间戳
return function (...args) {
const now = Date.now();
const context = this;
if (now - previous > wait) {
func.apply(context, args);
previous = now; // 更新执行时间戳
}
};
}
2. 定时器方式(延迟执行)
首次触发后等待固定时间执行,之后在固定间隔内忽略后续触发。
javascript
function throttle(func, wait) {
let timeout = null;
return function (...args) {
const context = this;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null; // 重置定时器
func.apply(context, args);
}, wait);
}
};
}
3. 结合时间戳和定时器(首尾均执行)
首次触发立即执行,最后一次触发在间隔结束后再执行一次。
javascript
function throttle(func, wait) {
let previous = 0;
let timeout = null;
return function (...args) {
const context = this;
const now = Date.now();
const remaining = wait - (now - previous);
if (remaining <= 0) {
// 立即执行
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
func.apply(context, args);
previous = now;
} else if (!timeout) {
// 设置最后一次执行的定时器
timeout = setTimeout(() => {
func.apply(context, args);
timeout = null;
previous = Date.now();
}, remaining);
}
};
}
四、代码解析
-
参数说明:
-
func
:需要节流的原始函数。 -
wait
:节流间隔时间(单位:毫秒)。
-
-
闭包保存状态:
previous
(时间戳方式)或timeout
(定时器方式)通过闭包保存状态,确保多次触发共享同一计时器。
-
apply
方法的作用:-
确保原始函数
func
的this
指向正确(如事件触发的元素)。 -
传递事件参数(如
event
对象)。
-
-
不同实现方式的区别:
-
时间戳方式:立即响应首次触发,适合需要即时反馈的场景(如按钮点击)。
-
定时器方式:延迟响应首次触发,适合连续触发但不需要立即执行的场景(如滚动事件)。
-
结合方式:兼顾首尾执行,适用于需要更平滑响应的场景(如动画)。
-
五、使用示例
1. 滚动事件节流(时间戳方式)
html
<div id="scrollArea" style="height: 2000px;"></div>
<script>
const scrollArea = document.getElementById('scrollArea');
// 原始滚动处理函数
function handleScroll() {
console.log('滚动位置:', window.scrollY);
}
// 节流处理(时间戳方式)
const throttledScroll = throttle(handleScroll, 200);
// 绑定滚动事件
window.addEventListener('scroll', throttledScroll);
</script>
2. 按钮防重复点击(定时器方式)
html
<button id="clickBtn">点击</button>
<script>
const clickBtn = document.getElementById('clickBtn');
// 原始点击处理函数
function handleClick() {
console.log('按钮点击');
}
// 节流处理(定时器方式)
const throttledClick = throttle(handleClick, 1000);
// 绑定点击事件
clickBtn.addEventListener('click', throttledClick);
</script>
六、总结
-
节流的核心逻辑:通过时间戳或定时器控制高频事件的执行频率。
-
实现要点:
-
使用
Date.now()
或setTimeout
管理间隔时间。 -
通过闭包保存计时器或时间戳状态。
-
处理
this
指向和参数传递。
-
-
应用场景:
-
滚动事件触发加载更多内容。
-
鼠标移动时更新元素位置(如拖拽)。
-
防止按钮频繁点击导致的重复提交。
-
对比:防抖 vs 节流
1. 防抖(debounce)
-
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
javascript//防抖简单写法 function debounce(func, t) { let timer = null return function () { if (timer) { clearTimeout(timer) } timer = setTimeout(() => func(), t) } }
2. 节流(throttle)
-
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
javascript//节流简单写法 function throttle(func, t) { let timer = null return function () { if (!timer) { timer = setTimeout(() => { func() timer = null }, t) } } }
3. 对比:
特性 | 防抖(Debounce) | 节流(Throttle) |
---|---|---|
触发频率 | 最后一次触发后等待 wait 时间执行 |
固定时间间隔内最多执行一次 |
适用场景 | 输入框搜索、窗口大小调整 | 滚动事件、鼠标移动事件、频繁点击按钮 |
核心目标 | 确保高频触发时只执行一次 | 确保高频触发时按固定频率执行 |
源码解析
一、_.throttle()
源码解析
1. 核心逻辑
节流函数确保在 wait
时间间隔内最多执行一次 func
,支持首次(leading
)和末次(trailing
)执行控制。
2. 关键源码步骤
javascript
_.throttle(func, [wait=0], [options={}])
javascript
function throttle(func, wait, options) {
let leading = true;
let trailing = true;
// 参数处理
if (typeof options === 'object') {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
let lastArgs, lastThis, result;
let timeout = null;
let previous = 0;
const throttled = function(...args) {
const now = Date.now();
// 首次调用且不执行 leading 时,设置 previous 为 now
if (!previous && leading === false) previous = now;
// 计算剩余时间
const remaining = wait - (now - previous);
// 需要执行(剩余时间 <=0 或系统时间被修改)
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(this, args);
} else if (!timeout && trailing !== false) {
// 设置尾调用的定时器
timeout = setTimeout(() => {
previous = leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(lastThis, lastArgs);
}, remaining);
}
return result;
};
// 提供取消方法
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = lastArgs = lastThis = null;
};
return throttled;
}
3. 核心特性
-
时间戳与定时器结合 :既保证首次触发立即响应(
leading
),又在停止触发后执行最后一次(trailing
)。 -
系统时间篡改兼容 :检测到
remaining > wait
时强制触发。 -
取消机制:允许手动取消未执行的尾调用。
二、_.debounce()
源码解析
1. 核心逻辑
防抖函数在连续触发时,仅在最后一次触发后等待 wait
时间执行一次 func
,支持立即执行模式(leading
)。
2. 关键源码步骤
javascript
_.debounce(func, [wait=0], [options={}])
javascript
function debounce(func, wait, options) {
let lastArgs, lastThis, result;
let timerId = null;
let lastCallTime = 0;
let leading = false;
let maxing = false;
let maxWait;
// 参数处理
if (typeof options === 'object') {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? Math.max(options.maxWait || 0, wait) : maxWait;
}
const invokeFunc = (time) => {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined;
result = func.apply(thisArg, args);
return result;
};
const leadingEdge = (time) => {
// 记录最后一次调用时间
lastCallTime = time;
// 设置定时器
timerId = setTimeout(timerExpired, wait);
// 立即执行模式
return leading ? invokeFunc(time) : result;
};
const shouldInvoke = (time) => {
// 判断是否需要执行(超过 wait 或 maxWait)
const timeSinceLastCall = time - lastCallTime;
return (lastCallTime === 0) || (timeSinceLastCall >= wait) ||
(maxing && timeSinceLastCall >= maxWait);
};
const debounced = function(...args) {
const time = Date.now();
lastArgs = args;
lastThis = this;
// 判断是否应该执行
const isInvoking = shouldInvoke(time);
if (isInvoking) {
// 清除已有定时器
if (timerId === null) {
return leadingEdge(time);
}
// 处理 maxWait 场景
if (maxing) {
timerId = setTimeout(timerExpired, wait);
return invokeFunc(time);
}
}
// 设置/重置定时器
if (timerId === null) {
timerId = setTimeout(timerExpired, wait);
}
return result;
};
// 提供取消和立即执行方法
debounced.cancel = function() { /* ... */ };
debounced.flush = function() { /* ... */ };
return debounced;
}
3. 核心特性
-
maxWait
支持:确保在超时后强制执行,避免长期不触发导致的延迟。 -
立即执行模式 :
leading
选项允许首次触发立即执行。 -
灵活控制 :
cancel
和flush
方法提供外部控制能力。