节流和防抖
防抖(debounce)
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
javascript
// 防抖函数
function debounce(fn, wait) {
let timer;
return function () {
let _this = this;
let args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args);
}, wait);
};
} // 使用window.onresize = debounce(function () { console.log("resize");}, 500);这段代码实现了 JavaScript 中的防抖(debounce) 函数,用于限制函数在短时间内的频繁执行。下面从基础到深入,逐行解析其中的知识点:
1. 防抖(Debounce)的核心概念 防抖的作用是:当事件被频繁触发时,只在最后一次触发后的指定时间(wait)后执行函数。
例如:输入框搜索联想(停止输入 wait 毫秒后再发送请求)、窗口 resize 事件(调整结束后再计算布局)。
2. 函数结构解析
javascript
function debounce(fn, wait) { ... }debounce是一个高阶函数(接收函数作为参数,并返回新函数)。- 参数:
fn:需要防抖处理的原函数(如搜索请求函数)。wait:防抖等待时间(毫秒),即触发后等待多久才执行fn。
3. 闭包与定时器变量 timer
javascript
let timer; // 定时器变量,存储 setTimeout 的返回值timer定义在debounce函数内部,被返回的匿名函数引用,形成闭包。- 作用:在多次调用防抖函数时,共享同一个
timer变量,用于清除上一次的定时器。
4. 返回的防抖处理函数
javascript
return function () { ... }- 返回一个新函数(防抖处理后的函数),替代原函数执行。
- 这个函数会在事件触发时被调用(如输入框的
input事件、按钮的click事件)。
5. 保存上下文与参数
javascript
let _this = this; // 保存当前上下文(this 指向)
let args = arguments; // 保存函数参数this指向问题:
原函数fn可能依赖调用时的上下文(如 DOM 事件中,this指向触发事件的元素)。这里用_this保存返回函数被调用时的this,确保fn执行时上下文正确。arguments对象:
保存返回函数被调用时接收的所有参数(如事件对象event),确保传递给原函数fn。
6. 清除上一次的定时器
javascript
if (timer) {
clearTimeout(timer); // 清除已有定时器
}- 每次触发事件时,先清除上一次设置的定时器,避免原函数被提前执行。
- 例如:输入框快速输入时,每次按键都会清除上一次的定时器,直到停止输入后,最后一次定时器才会生效。
7. 设置新的定时器
javascript
timer = setTimeout(() => {
fn.apply(_this, args); // 延迟执行原函数
}, wait);- 重新设置定时器,
wait毫秒后执行回调函数。 - 回调中通过
fn.apply(_this, args)调用原函数:apply确保fn的this指向_this(正确的上下文)。args作为参数传递给fn(确保参数完整)。
8. 完整执行流程示例 以输入框搜索为例,假设 wait = 500ms,用户快速输入 "abc":
- 输入 "a" → 触发事件 →
timer为undefined→ 设置定时器(500ms 后执行搜索)。 - 300ms 后输入 "b" → 触发事件 → 清除上一次定时器 → 重新设置定时器(500ms 后执行)。
- 又过 300ms 后输入 "c" → 触发事件 → 清除上一次定时器 → 重新设置定时器。
- 停止输入,500ms 后定时器触发 → 执行搜索函数(仅执行一次,参数为 "abc")。
9. 可能的优化点
- 立即执行版:添加
immediate参数,控制是否在第一次触发时立即执行,后续触发才防抖。 - 返回值处理:原函数
fn的返回值无法直接获取(因被包裹在setTimeout中),需用回调或 Promise 处理。 - 类型定义:在 TypeScript 中添加类型注解,提升类型安全性。
节流(throttle)
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
javascript
// 方式1: 使用时间戳
function throttle1(fn, wait) {
let time = 0;
return function () {
let _this = this;
let args = arguments;
let now = Date.now();
if (now - time > wait) {
fn.apply(_this, args);
time = now;
}
};
}
// 方式2: 使用定时器
function thorttle2(fn, wait) {
let timer;
return function () {
let _this = this;
let args = arguments;
if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(_this, args);
}, wait);
}
};
}上面节流和防抖实现方式比较简单,但是已经可以满足日常使用,如果想更近一步了解可以查看underscore和lodash文档中debounce和thorttle