鲁斯前端布鲁斯前端

文章中英模式

常见的前端面试题目 - 用户交互优化 - Throttle 与 Debounce

深入理解 JavaScript 中的 Throttle 节流与 Debounce 防抖技术,包含两者差异、实现原理、适用场景详解与实际范例,掌握前端性能优化与面试常考题目。

影片縮圖

懒得看文章?那就来看视频吧

什么是 Throttle 与 Debounce?为什么需要它们?

Throttle(节流)和 Debounce(防抖)是两种常用的前端性能优化技术,用于控制高频事件(如滚动、搜索、调整窗口大小)的执行频率。

  • Throttle(节流):限制函数在一段时间内只执行一次,无论触发多少次。像是「每隔 N 毫秒最多执行一次」。
  • Debounce(防抖):将多次连续触发合并为一次,只在最后一次触发后等待一段时间才执行。像是「等待 N 毫秒没有新触发才执行」。

这两种技术对于优化前端性能、提升用户体验非常重要:

  • 减少不必要的函数调用
  • 降低 API 请求频率
  • 优化资源密集型计算
  • 减少 DOM 操作频率

Throttle(节流)实现与原理

节流的核心概念是「保证在指定时间内,函数最多只能执行一次」:

function throttle(func, delay) {
  let lastCall = 0;
  
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= delay) {
      func.apply(this, args);
      lastCall = now;
    }
  };
}

// 使用范例
const throttledScroll = throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 300);

window.addEventListener('scroll', throttledScroll);

图解 Throttle 执行原理:

触发事件:↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
执行函数:↓        ↓        ↓
时间轴:  |--------|--------|------->
          300ms    300ms    300ms

Debounce(防抖)实现与原理

防抖的核心概念是「等待指定时间内没有新的调用再执行函数」:

function debounce(func, delay) {
  let timer = null;
  
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 使用范例
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((e) => {
  console.log('搜索关键字:', e.target.value);
  // fetchResults(e.target.value);
}, 500);

searchInput.addEventListener('input', debouncedSearch);

图解 Debounce 执行原理:

触发事件:↓  ↓  ↓      ↓ ↓          ↓
重置计时:⟳  ⟳  ⟳      ⟳ ⟳          ⟳
执行函数:                ✓            ✓
时间轴:  |-----|-----|-----|-----|---->
        等待中  等待中 执行   等待中 执行

使用场景比较

Throttle 与 Debounce 适用场景比较:

技术适用场景实际应用
Throttle(节流)滚动事件处理社交媒体的无限滚动加载,每300ms检查一次滚动位置
游戏中的按键控制射击游戏中限制玩家每秒最多发射5颗子弹
地图拖动/缩放Google地图拖动时,每100ms才重新请求地图数据
Canvas 绘图绘图应用中,笔刷跟随鼠标以固定间隔绘制点
Debounce(防抖)搜索框输入Google搜索框停止输入500ms后才发送搜索请求
表单验证注册表单中,用户输入完邮箱后才检查是否已被使用
自动保存Google文档在用户停止输入2秒后自动保存内容
窗口大小调整响应式网站在用户完成调整窗口大小后才重新排版

实际应用案例与效能差异

案例 1:滚动事件优化(使用 Throttle)

未优化前,滚动时可能会触发上百次函数调用:

// 未优化 - 每次滚动都会执行
window.addEventListener('scroll', () => {
  loadMoreContent();
});

// 优化后 - 每 200ms 最多执行一次
window.addEventListener('scroll', throttle(() => {
  loadMoreContent();
}, 200));

案例 2:自动保存表单(使用 Debounce)

未优化前,输入每个字都会触发保存:

// 未优化 - 每次输入都会保存
editor.addEventListener('input', saveContent);

// 优化后 - 停止输入 500ms 后才保存
editor.addEventListener('input', debounce(saveContent, 500));

🔥 常见面试题目

(一)Throttle 和 Debounce 的主要区别是什么?

解答:Throttle(节流)控制函数在一段时间内最多执行一次,无论触发多少次,适合持续性事件如滚动。例如:在电商网站上,当用户滚动页面时,使用 Throttle 每 200ms 检查一次是否需要加载更多商品,而不是每次滚动都触发。Debounce(防抖)则是将多次连续触发合并为一次,只在最后一次触发后等待一段时间才执行,适合连续触发但只需最终结果的场景如搜索。例如:在搜索框中,用户快速输入「React 教学」时,使用 Debounce 等待用户停止输入 500ms 后才发送 API 请求,而不是每打一个字就发送一次请求。

(二)以下代码的执行结果是什么?

let count = 0;

const btn = document.getElementById('btn');
const throttledClick = throttle(() => {
  console.log(`點擊次數: ${++count}`);
}, 1000);

// 假設使用者在 2 秒內快速點擊 5 次
btn.addEventListener('click', throttledClick);
解答:假设用户在 2 秒内点击 5 次,console 会输出:
  1. 第 0 秒时:「点击次数: 1」(首次立即执行)
  2. 第 1 秒时:「点击次数: 2」(间隔满 1 秒后执行)
  3. 第 2 秒时:「点击次数: 3」(间隔满 1 秒后执行)
不会执行 5 次,而是按照 throttle 的时间间隔,每秒最多执行一次。

(三)以下 debounce 代码的执行结果是什么?

let count = 0;

const searchInput = document.getElementById('search');
const debouncedSearch = debounce(() => {
  console.log(`搜尋次數: ${++count}`);
}, 500);

// 假設使用者在 1 秒內快速輸入 5 個字元
searchInput.addEventListener('input', debouncedSearch);
解答:假设用户在 1 秒内快速输入 5 个字符,console 会输出:
  1. 第 1.5 秒时:「搜索次数: 1」(最后一次输入后 500ms 执行)
不会执行 5 次,而是按照 debounce 的原理,只在最后一次输入事件后等待 500ms 才执行一次。如果用户在计时器结束前又输入了新字符,则重新计时。这就是为什么 debounce 适合用于搜索框等需要等待用户停止输入后再执行操作的场景。

(四)实现一个简单的搜索框 Debounce

解答:以下是一个简单的搜索框 Debounce 实现:

// HTML: <input id="search" type="text" placeholder="搜索...">
// HTML: <div id="results"></div>

function debounce(func, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => func(...args), delay);
  };
}

const searchInput = document.getElementById('search');
const resultsDiv = document.getElementById('results');

const handleSearch = debounce((event) => {
  const query = event.target.value;
  resultsDiv.textContent = `搜索中: ${query}`;
  // 实际应用中这里会发送 API 请求
}, 300);

searchInput.addEventListener('input', handleSearch);