什么是防抖节流?
防抖(Debounce)
定义
防抖是一种限制函数调用频率的技术,它的核心思想是在一定时间内,只有最后一次调用函数才会被执行。如果在这个时间间隔内函数被多次调用,那么每次调用都会重置计时,直到最后一次调用后的指定时间结束,函数才会真正执行。
示例场景
- 搜索框输入提示:在网页的搜索框中,当用户输入关键词时,通常会根据输入内容实时显示搜索提示。如果不使用防抖,用户每输入一个字符都会触发一次请求,这会导致大量不必要的请求发送到服务器,增加服务器压力。使用防抖后,只有当用户停止输入一段时间(例如 300 毫秒)后,才会发送请求获取搜索提示,减少了请求次数。
- 窗口大小改变事件 :当浏览器窗口大小改变时,会触发
resize
事件。如果在这个事件处理函数中执行一些复杂的布局计算或重绘操作,不使用防抖的话,窗口大小改变过程中会频繁触发这些操作,影响性能。使用防抖可以确保只有在用户停止改变窗口大小一段时间后,才执行相应的计算和重绘。
节流(Throttle)
定义
节流也是一种限制函数调用频率的技术,它规定在一个固定的时间间隔内,函数只能被调用一次。如果在这个时间间隔内多次触发函数调用,只有第一次调用会被执行,后续的调用会被忽略,直到下一个时间间隔开始。
示例场景
- 滚动加载更多:在一些网页的滚动加载场景中,当用户滚动页面到底部时,会触发加载更多数据的操作。如果不使用节流,用户快速滚动页面时会频繁触发加载请求,可能会导致数据重复加载或服务器压力过大。使用节流可以确保在一定时间内(例如 500 毫秒),只触发一次加载请求。
- 按钮点击限制:对于一些需要避免用户频繁点击的按钮,如提交表单按钮、点赞按钮等,可以使用节流来限制按钮点击的响应频率,防止用户在短时间内多次点击造成不必要的操作。
防抖节流实验
未使用防抖节流
我们做下面的对比实验:
还没有使用防抖节流,代码如下:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>防抖节流对比示例</title>
<style>
body {
font-family: Arial, sans-serif;
}
label {
display: block;
margin-bottom: 5px;
}
input {
margin-bottom: 10px;
padding: 5px;
}
</style>
</head>
<body>
<h2>未使用防抖节流</h2>
<label for="originalInput">搜索框:</label>
<input type="text" id="originalInput" placeholder="输入内容" />
<h2>使用防抖</h2>
<label for="debounceInput">搜索框:</label>
<input type="text" id="debounceInput" placeholder="输入内容" />
<h2>使用节流</h2>
<label for="throttleInput">搜索框:</label>
<input type="text" id="throttleInput" placeholder="输入内容" />
<script>
//没有防抖节流
function handleOriginalInput() {
console.log("未使用防抖节流,触发搜索请求,输入内容为:", this.value);
}
const originalInput = document.getElementById("originalInput");
originalInput.addEventListener("input", handleOriginalInput);
// todo : 防抖函数
// todo : 节流函数
</script>
</body>
</html>
使用防抖
在上面 html 添加如下代码:
javascript
function debounce(func, delay) {
let timer;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
const debounceInput = document.getElementById("debounceInput");
// 应用防抖函数到输入框的 input 事件
const debouncedInputHandler = debounce(handleDebounceInput, 500);
debounceInput.addEventListener("input", debouncedInputHandler);
这段代码实现了一个防抖函数,下面详细解释为什么要这么写:
整体思路
防抖函数的核心目的是在一定时间内,只有最后一次调用函数才会被执行。如果在这个时间间隔内函数被多次调用,那么每次调用都会重置计时,直到最后一次调用后的指定时间结束,函数才会真正执行。为了实现这个功能,需要借助闭包和定时器来完成。
代码逐行解释
1. 函数定义和参数
javascript
function debounce(func, delay) {
debounce
是一个高阶函数,它接受两个参数:func
:需要进行防抖处理的原始函数。delay
:防抖的时间间隔,单位为毫秒。
2. 声明定时器变量
javascript
let timer;
timer
用于存储定时器的 ID。由于timer
是在debounce
函数内部声明的,它会形成一个闭包,使得在返回的匿名函数中可以访问和修改这个变量。
3. 返回一个新函数
javascript
return function () {
debounce
函数返回一个新的匿名函数,这个新函数包含了防抖的逻辑。当调用debounce
函数时,实际上是在返回这个新函数,后续调用这个新函数时,就会触发防抖处理。
4. 保存上下文和参数
javascript
const context = this;
const args = arguments;
context
:保存当前的执行上下文(this
值),确保在延迟执行func
时,this
的指向不变。args
:保存调用新函数时传递的参数,以便在延迟执行func
时可以将这些参数传递给它。
5. 清除定时器
javascript
clearTimeout(timer);
- 每次调用新函数时,先清除之前设置的定时器。这是防抖的关键步骤,如果在延迟时间内再次调用了新函数,就会清除之前的定时器,重新开始计时。
6. 设置新的定时器
javascript
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
- 使用
setTimeout
函数设置一个新的定时器,在delay
毫秒后执行func
函数。 func.apply(context, args)
:使用apply
方法调用func
函数,并将之前保存的执行上下文context
和参数args
传递给它,确保func
函数在正确的上下文和参数下执行。
通过这种方式,每次调用新函数时都会重置定时器,只有在最后一次调用后的 delay
毫秒内没有再次调用,定时器才会正常执行,从而实现了防抖的效果。
使用节流
在 html 中添加如下代码:
javascript
function throttle(func, delay) {
let timer = null;
return function () {
const context = this;
const args = arguments;
if (!timer) {
func.apply(context, args);
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
}
const throttleInput = document.getElementById("throttleInput");
// 应用节流函数到输入框的 input 事件
const throttledInputHandler = throttle(handleThrottleInput, 1000);
throttleInput.addEventListener("input", throttledInputHandler);
这段代码实现了一个节流函数,下面为你详细解释其实现原理和各部分代码的作用。
节流函数的概念
节流是一种限制函数调用频率的技术,它规定在一个固定的时间间隔内,函数只能被调用一次。如果在这个时间间隔内多次触发函数调用,只有第一次调用会被执行,后续的调用会被忽略,直到下一个时间间隔开始。
代码详细解释
1. 函数定义和参数
javascript
function throttle(func, delay) {
throttle
是一个高阶函数,它接收两个参数:func
:需要进行节流处理的原始函数,即你想要限制其调用频率的函数。delay
:节流的时间间隔,单位为毫秒。这个时间间隔决定了在多长时间内,func
函数只能被调用一次。
2. 声明定时器变量
javascript
let timer = null;
timer
用于存储定时器的 ID。初始值设为null
,表示当前没有正在计时的定时器。由于timer
是在throttle
函数内部声明的,它会形成一个闭包,使得在返回的匿名函数中可以访问和修改这个变量。
3. 返回一个新函数
javascript
return function () {
throttle
函数返回一个新的匿名函数。当调用throttle
函数时,实际上是在返回这个新函数,后续调用这个新函数时,就会触发节流处理。
4. 保存上下文和参数
javascript
const context = this;
const args = arguments;
context
:保存当前的执行上下文(this
值)。因为在 JavaScript 中,函数内部的this
指向可能会发生变化,通过保存this
的值,可以确保在后续调用func
函数时,this
的指向不变。args
:保存调用新函数时传递的参数。使用arguments
对象可以获取所有传递给当前函数的参数,以便在调用func
函数时将这些参数传递给它。
5. 判断是否可以执行函数
javascript
if (!timer) {
- 检查
timer
是否为null
。如果timer
为null
,说明当前没有正在计时的定时器,即当前处于可以执行func
函数的状态。
6. 执行函数并设置定时器
javascript
func.apply(context, args);
timer = setTimeout(() => {
timer = null;
}, delay);
func.apply(context, args)
:使用apply
方法调用func
函数,并将之前保存的执行上下文context
和参数args
传递给它,确保func
函数在正确的上下文和参数下执行。setTimeout(() => { timer = null; }, delay)
:设置一个定时器,在delay
毫秒后将timer
重置为null
。这意味着在delay
时间内,timer
不再为null
,后续调用新函数时,由于if (!timer)
条件不满足,func
函数不会被执行,从而实现了节流的效果。
通过上述代码,throttle
函数实现了对 func
函数调用频率的限制。在 delay
时间间隔内,func
函数只能被调用一次,从而避免了函数被频繁调用,减少了不必要的计算和资源消耗,提高了性能。
真枪实弹
做一个前后端的实验来体验一下,防抖节流的操作。
前端
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖节流前后端对比示例</title>
<style>
body {
font-family: Arial, sans-serif;
}
label {
display: block;
margin-bottom: 5px;
}
input {
margin-bottom: 10px;
padding: 5px;
}
</style>
</head>
<body>
<h2>未使用防抖节流</h2>
<label for="originalInput">搜索框:</label>
<input type="text" id="originalInput" placeholder="输入内容">
<h2>使用防抖</h2>
<label for="debounceInput">搜索框:</label>
<input type="text" id="debounceInput" placeholder="输入内容">
<h2>使用节流</h2>
<label for="throttleInput">搜索框:</label>
<input type="text" id="throttleInput" placeholder="输入内容">
<script>
// 防抖函数
function debounce(func, delay) {
let timer;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
// 节流函数
function throttle(func, delay) {
let timer = null;
return function () {
const context = this;
const args = arguments;
if (!timer) {
func.apply(context, args);
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
}
// 发送搜索请求的函数
function sendSearchRequest(inputId) {
const input = document.getElementById(inputId);
const keyword = input.value;
fetch(`/search?keyword=${encodeURIComponent(keyword)}`)
.then(response => response.json())
.then(data => {
console.log(`来自 ${inputId} 的搜索结果:`, data);
})
.catch(error => {
console.error('请求出错:', error);
});
}
// 未使用防抖节流时的处理函数
const originalInput = document.getElementById('originalInput');
originalInput.addEventListener('input', function () {
sendSearchRequest('originalInput');
});
// 防抖后的处理函数
const debounceInput = document.getElementById('debounceInput');
const debouncedSearch = debounce(() => sendSearchRequest('debounceInput'), 500);
debounceInput.addEventListener('input', debouncedSearch);
// 节流后的处理函数
const throttleInput = document.getElementById('throttleInput');
const throttledSearch = throttle(() => sendSearchRequest('throttleInput'), 300);
throttleInput.addEventListener('input', throttledSearch);
</script>
</body>
</html>
后端
javascript
const express = require('express');
const app = express();
const port = 3000;
// 模拟搜索接口
app.get('/search', (req, res) => {
const keyword = req.query.keyword;
console.log(`收到搜索请求,关键词: ${keyword}`);
// 模拟返回搜索结果
const result = {
message: `搜索结果为关于 ${keyword} 的内容`
};
res.json(result);
});
app.use(express.static('.'));
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});