# JavaScript防抖与节流机制详解

# 1. 防抖机制

# 1.1 概念理解

防抖(Debounce)是一种限制函数执行频率的技术。可以理解为电梯的工作原理:当电梯准备关门时,如果有人进入,电梯会重新开始计时,直到等待时间结束才关门。在前端开发中,防抖常用于处理用户频繁触发的事件,如输入框搜索、窗口大小调整等。

# 1.2 工作原理

防抖的核心思想是:事件被触发后,函数不会立即执行,而是等待一段时间(延迟时间)后再执行。如果在这段时间内事件再次被触发,则重新计算等待时间。

# 1.3 防抖实现

# 1.3.1 基础版本

基础版本的防抖函数在事件触发后等待指定时间再执行函数,如果在等待期间再次触发事件,则重新计时:

function debounce (func, wait) {
  let timeout;
  return function () {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      // eslint-disable-next-line prefer-rest-params
      func.apply(this, arguments);
    }, wait);
  };
}
1
2
3
4
5
6
7
8
9
10

# 1.3.2 立即执行版本

立即执行版本在事件触发后立即执行函数,然后在指定时间内不再触发事件才能继续执行:

function debounce (func, wait) {
  let timeout;
  return function () {
    clearTimeout(timeout);
    // eslint-disable-next-line prefer-rest-params
    if (!timeout) func.apply(this, arguments);
    timeout = setTimeout(() => {
      timeout = null;
    }, wait);
  };
}
1
2
3
4
5
6
7
8
9
10
11

# 1.3.3 完整版本

完整版本支持选择立即执行或延迟执行模式:

/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce (func, wait, immediate) {
  let timeout;
  return function () {
    clearTimeout(timeout);
    if (immediate) {
      // eslint-disable-next-line prefer-rest-params
      if (!timeout) func.apply(this, arguments);
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
    } else {
      timeout = setTimeout(() => {
        // eslint-disable-next-line prefer-rest-params
        func.apply(this, arguments);
      }, wait);
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 1.4 应用场景

防抖技术适用于以下场景:

  1. 搜索框输入:用户在搜索框中输入时,不需要每次按键都发送请求,只需在用户停止输入后再发送请求。
  2. 表单验证:在用户输入手机号、邮箱等信息时,不需要每次输入都进行验证,可以在用户停止输入后再进行验证。
  3. 窗口大小调整:在用户调整窗口大小时,不需要每次调整都重新计算布局,只需在用户停止调整后再计算。

# 2. 节流机制

# 2.1 概念理解

节流(Throttle)是一种限制函数执行频率的技术,确保函数在指定时间间隔内最多执行一次。可以将其想象成公交车的发车机制:无论有多少人在等车,公交车都会按照固定的时间间隔发车,不会因为等待的人多就频繁发车。在前端开发中,节流常用于处理高频触发的事件,如滚动条滚动、鼠标移动、页面缩放等,以避免函数执行过于频繁导致性能问题。

# 2.2 工作原理

节流的核心思想是:连续触发事件时,函数在指定时间间隔内只执行一次。节流会稀释函数的执行频率,确保函数不会被过于频繁地调用。

# 2.3 节流实现

# 2.3.1 时间戳版本

使用时间戳实现节流,当触发事件时,取出当前时间戳,减去之前的时间戳(初始值为0),如果差值大于设置的时间周期,则执行函数并更新时间戳:

function throttle (func, wait) {
  let previous = 0;
  return function () {
    const now = Date.now();
    if (now - previous > wait) {
      // eslint-disable-next-line prefer-rest-params
      func.apply(this, arguments);
      previous = now;
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11

# 2.3.2 定时器版本

使用定时器实现节流,在指定时间间隔后执行函数:

function throttle (func, wait) {
  let timeout;
  return function () {
    if (!timeout) {
      timeout = setTimeout(() => {
        // eslint-disable-next-line prefer-rest-params
        func.apply(this, arguments);
        timeout = null;
      }, wait);
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12

# 2.3.3 完整版本

完整版本结合了时间戳和定时器两种方式的优点: 重点:

  • 支持最简单的情况使用时间戳判断实现,最优性能
  • 首次调用后,第二次执行设置定时器的时间需要计算差值
/**
 * 基于定时器+时间戳实现(保证能够触发最后一次调用、第一次执行立即调用、超过 wait 时间后执行立即调用)
 * @param {Function} func
 * @param {boolean} wait
 * @param {boolean} leading 是否第一次立即调用
 * @param {boolean} trailing 是否保证末尾调用
 */
function throttle(func, wait, leading, trailing) {
  let timer;
  let lastTime = 0;
  return function throttled(...args) {
    if (timer) {
      return;
    }
    const now = Date.now();
    if (!leading) {
      lastTime = now;
    }
    const remaining = wait - (now - lastTime);
    const call = () => {
      func.apply(this, args);
      lastTime = now;
      timer = null;
    };
    if (remaining <= 0) {
      call();
    } else if (trailing) {
      timer = setTimeout(() => {
        call();
      }, remaining);
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 2.4 应用场景

节流技术适用于以下场景:

  1. 鼠标点击事件:在用户连续点击按钮时,限制单位时间内的触发次数,防止重复提交。
  2. 滚动事件监听:在用户滚动页面时,监听是否滑到底部以自动加载更多内容,使用节流来控制函数执行频率。
  3. 即时搜索联想:在用户输入搜索关键词时,使用节流来控制联想词的请求频率,避免过多请求。

# 3. 参考文档

什么是防抖和节流?有什么区别?如何实现? (opens new window)

7分钟理解JS的节流、防抖及使用场景 (opens new window)

Last Updated: 8/29/2025, 5:36:30 PM