前序
函数防抖(Debouncing)和函数节流(Throttling)都是用于控制函数执行频率的技术,通常在处理高频率触发的事件(如窗口滚动、鼠标移动、输入框输入等)时非常有用
一、核心概念
函数防抖
函数防抖的核心思想是,在某个事件持续触发时,只有当事件停止触发一段时间后,才执行相应的函数。这意味着如果事件在一定时间内持续触发,函数将不会被执行,直到事件停止触发并且等待一定的延迟时间后才执行。
关键概念:
- 触发事件时,设置一个定时器,延迟一定时间后执行函数。
- 如果事件再次触发,清除之前的定时器,重新设置新的定时器。
- 只有在事件停止触发一段时间后,定时器才会执行函数。
函数节流
函数节流的核心思想是,在某个事件持续触发时,控制函数的执行频率,确保函数在一定时间间隔内最多只能执行一次。与函数防抖不同,函数节流会按照一定的时间间隔持续执行函数,而不会等待事件停止触发。
关键概念:
- 触发事件时,设置一个定时器,在定时器时间间隔内只允许执行一次函数。
- 如果事件再次触发,即使定时器还在计时,也不会执行函数,直到定时器计时结束。
二、函数防抖和节流的代码实现
函数防抖的实现:
函数防抖的关键在于使用 setTimeout 来延迟执行函数,如果在延迟期间再次触发事件,则取消之前的延迟,重新设置新的延迟。
// 定义一个防抖函数
function debounce(func, delay) {
let timeoutId; // 保存延迟执行的定时器的ID
// 返回一个包装函数
return function (...args) {
clearTimeout(timeoutId); // 清除之前的定时器
// 创建一个新的定时器,在延迟时间后执行函数
timeoutId = setTimeout(() => {
func.apply(this, args); // 执行函数,并传递参数
}, delay);
};
}
// 创建一个被防抖的函数
const debouncedFunction = debounce(() => {
console.log("Debounced function executed.");
}, 1000);
// 某个事件触发时调用 debouncedFunction
debouncedFunction();
函数节流的实现:
函数节流的关键在于使用一个标志变量来控制函数是否可以执行,以及设置一个定时器,在定时器内将标志变量重置,以便在下一次时间间隔内执行函数。
// 定义一个节流函数
function throttle(func, delay) {
let canRun = true; // 标记函数是否可执行,默认为可执行
// 返回一个包装函数
return function (...args) {
if (!canRun) return; // 如果函数不可执行,则直接返回,不继续执行以下代码
canRun = false; // 将函数标记为不可执行
// 创建一个延迟定时器,在延迟时间过后执行函数
setTimeout(() => {
func.apply(this, args); // 执行函数,并传递参数
canRun = true; // 将函数标记为可执行
}, delay);
};
}
// 创建一个被节流的函数
const throttledFunction = throttle(() => {
console.log("Throttled function executed.");
}, 1000);
// 某个事件触发时调用 throttledFunction
throttledFunction();
三、函数防抖和节流的优化
函数防抖(Debouncing)和函数节流(Throttling)的实现可以进一步优化,以满足不同的需求和性能要求。下面是一些可能的优化方式:
函数防抖的优化:
立即执行:有时候,可能希望在事件触发时立即执行一次函数,然后再进行防抖延迟。这可以通过设置一个参数来实现。
// 定义一个防抖函数,可以选择是否立即执行
function debounce(func, delay, immediate) {
let timeoutId; // 声明一个变量用于保存定时器的ID
// 返回一个闭包函数
return function (...args) {
const context = this; // 保存函数执行时的上下文(this指向)
const later = () => {
timeoutId = null; // 清空定时器的ID
if (!immediate) {
func.apply(context, args); // 如果不是立即执行,则调用函数
}
};
clearTimeout(timeoutId); // 清除之前的定时器
if (immediate && !timeoutId) {
// 如果是立即执行且之前没有定时器
func.apply(context, args); // 立即执行函数
}
timeoutId = setTimeout(later, delay); // 创建一个新的定时器,在延迟时间后执行later函数
};
}
// 创建一个被防抖的函数
const debouncedFunction = debounce(() => {
console.log("Debounced function executed.");
}, 1000, true);
// 某个事件触发时调用 debouncedFunction
debouncedFunction();
取消防抖:有时候,可能希望能够取消防抖,即立即执行函数并取消延迟。这可以通过返回一个取消函数来实现
// 定义一个防抖函数,根据延迟时间限制函数的执行频率
function debounce(func, delay) {
let timeoutId; // 声明一个变量用于保存定时器的ID
// 定义一个被防抖包装后的函数
function debounced(...args) {
const context = this; // 保存函数执行时的上下文(this指向)
const later = () => {
timeoutId = null; // 清除定时器ID
func.apply(context, args); // 调用函数
};
clearTimeout(timeoutId); // 清除之前的定时器
timeoutId = setTimeout(later, delay); // 创建一个新的定时器,在延迟时间后执行later函数
}
// 添加取消防抖的方法
debounced.cancel = function () {
clearTimeout(timeoutId); // 清除当前的定时器
};
return debounced; // 返回被防抖处理后的函数
}
函数节流的优化:
开始时立即执行:与函数防抖类似,可以添加一个参数,以便在时间间隔开始时立即执行一次函数。
// 定义一个节流函数
function throttle(func, delay, immediate) {
let canRun = true; // 标记函数是否可执行
// 返回一个包装函数
return function (...args) {
const context = this; // 保存函数执行时的上下文环境
// 如果 immediate 参数为 true 并且函数可以执行
if (immediate && canRun) {
func.apply(context, args); // 立即执行函数
canRun = false; // 将函数标记为不可执行
}
// 如果 immediate 参数为 false
if (!immediate) {
// 如果函数可以执行
if (canRun) {
canRun = false; // 将函数标记为不可执行
// 在延迟时间过后执行函数
setTimeout(() => {
func.apply(context, args); // 执行函数
canRun = true; // 将函数标记为可执行
}, delay);
}
}
};
}
返回最后一次执行的结果:有时候,可能需要函数节流的同时返回最后一次执行的结果。
// 定义一个节流函数
function throttle(func, delay) {
let canRun = true; // 标记函数是否可执行
let lastResult; // 保存上次执行函数的结果
// 返回一个包装函数
return function (...args) {
const context = this; // 保存函数执行时的上下文环境
// 如果函数可以执行
if (canRun) {
lastResult = func.apply(context, args); // 执行函数,并保存执行结果
canRun = false; // 将函数标记为不可执行
// 设置一个延迟时间,在延迟时间过后将函数标记为可执行
setTimeout(() => {
canRun = true;
}, delay);
}
return lastResult; // 返回上次执行函数的结果
};
}
四、函数防抖和节流的比较
函数防抖(Debouncing)和函数节流(Throttling)都是用于控制函数执行频率的技术,但它们在实际应用中有不同的用途和特点。以下是它们的比较:
1、目的和用途:
函数防抖的主要目的是确保在连续触发事件时,只有在事件停止触发一段时间后才执行相应的函数。它适用于需要等待用户停止操作后才执行的场景,如输入框实时搜索、窗口调整大小等。
函数节流的主要目的是控制函数的执行频率,确保函数在一定时间间隔内最多只能执行一次。它适用于需要限制函数执行频率的场景,如滚动事件、按钮点击防抖等。
2、执行时刻:
函数防抖在事件连续触发时,只有在事件停止触发一段时间后才执行一次函数。
函数节流会按照一定的时间间隔持续执行函数,无论事件是否连续触发。
3、响应速度:
函数防抖可能在事件停止后有一定的延迟,因为它等待一段时间以确保事件的停止。
函数节流在每个时间间隔内都会执行函数,因此响应速度更快,但仍受到时间间隔的限制。
4、实现方式:
函数防抖通常使用 setTimeout 来延迟函数的执行,并在每次触发事件时取消之前的延迟。
函数节流通过控制一个标志变量来限制函数的执行,定时器用于重置标志变量。
五、函数防抖和节流的应用场景
函数防抖(Debouncing)和函数节流(Throttling)是在处理高频触发的事件时非常有用的技术,它们可以改善用户体验,提高性能,以及减少不必要的函数执行。以下是它们在实际应用中常见的场景:
函数防抖的应用场景:
-
输入框实时搜索:当用户在搜索框中输入内容时,防抖可以确保只在用户停止输入一段时间后才触发搜索请求,以减轻服务器负担。
-
窗口调整大小:在窗口大小调整过程中,防抖可以确保只在用户停止拖动窗口边界后才执行重新布局的操作。
-
按钮点击防抖:防抖可以用于按钮点击事件,以防止用户多次点击按钮,确保只执行一次点击事件处理函数。
-
延迟执行:在需要延迟执行某个函数时,防抖可以用于确保只在一定时间内执行一次函数,例如延迟执行动画或提示消息。
-
滚动事件:在滚动事件处理中,防抖可以用于确保只在用户停止滚动页面一段时间后才执行事件处理函数,减少函数的执行次数。
函数节流的应用场景:
-
滚动事件:滚动事件可能会频繁触发,使用节流可以限制事件处理函数的执行频率,提高性能。
-
拖拽操作:在拖拽操作期间,使用节流可以确保只在一定时间间隔内执行拖拽事件处理函数,以平滑处理拖拽操作。
-
页面滚动加载:在无限滚动页面中,使用节流可以控制加载更多内容的触发频率,避免瞬间加载大量数据。
-
鼠标移动事件:在处理鼠标移动事件时,使用节流可以减少事件处理函数的执行次数,提高性能。
-
定时器更新:在需要定时更新某个状态或界面元素时,使用节流可以确保只在一定时间间隔内执行更新操作,避免频繁刷新。