Skip to content

防抖

防抖(Debounce)是一种常用的性能优化技术,用于减少函数的调用次数,提高代码的执行效率。它的核心思想是:在事件触发后的一段时间内,只执行最后一次函数调用,如果在这段时间内又触发了事件,则重新计时。

防抖的应用场景

防抖函数在以下场景中特别有用:

  1. 搜索框输入:当用户在搜索框中输入时,不需要每次按键都发送搜索请求,而是等待用户输入完成后再发送请求
  2. 表单验证:当用户在表单中输入时,不需要每次按键都验证表单,而是等待用户输入完成后再验证
  3. 窗口大小调整:当用户调整窗口大小时,不需要每次调整都重新计算布局,而是等待用户调整完成后再重新计算
  4. 滚动事件:当用户滚动页面时,不需要每次滚动都执行回调函数,而是等待用户滚动完成后再执行

防抖函数的实现

基本实现

防抖函数的基本实现思路是:使用一个定时器,在事件触发时启动定时器,如果在定时器到期前又触发了事件,则清除之前的定时器,重新启动一个新的定时器。当定时器到期时,执行回调函数。

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

带立即执行选项的实现

有时候,我们希望在事件触发后立即执行一次函数,然后在一段时间内不再执行,这种情况下可以添加一个immediate选项。

javascript
function debounce(func, delay, immediate = false) {
  let timer = null;
  return function() {
    const context = this;
    const args = arguments;
    
    // 如果定时器存在,清除定时器
    if (timer) {
      clearTimeout(timer);
    }
    
    // 如果是立即执行
    if (immediate) {
      // 如果定时器不存在,立即执行函数
      const callNow = !timer;
      // 设置定时器,在延迟后将定时器设为null
      timer = setTimeout(() => {
        timer = null;
      }, delay);
      // 如果是立即执行,调用函数
      if (callNow) {
        func.apply(context, args);
      }
    } else {
      // 非立即执行,设置定时器
      timer = setTimeout(() => {
        func.apply(context, args);
      }, delay);
    }
  };
}

带取消功能的实现

有时候,我们希望能够取消防抖函数的执行,这种情况下可以添加一个cancel方法。

javascript
function debounce(func, delay, immediate = false) {
  let timer = null;
  const debounced = function() {
    const context = this;
    const args = arguments;
    
    // 如果定时器存在,清除定时器
    if (timer) {
      clearTimeout(timer);
    }
    
    // 如果是立即执行
    if (immediate) {
      // 如果定时器不存在,立即执行函数
      const callNow = !timer;
      // 设置定时器,在延迟后将定时器设为null
      timer = setTimeout(() => {
        timer = null;
      }, delay);
      // 如果是立即执行,调用函数
      if (callNow) {
        func.apply(context, args);
      }
    } else {
      // 非立即执行,设置定时器
      timer = setTimeout(() => {
        func.apply(context, args);
      }, delay);
    }
  };
  
  // 添加取消方法
  debounced.cancel = function() {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
  };
  
  return debounced;
}

防抖函数的使用

基本使用

javascript
// 定义一个搜索函数
function search(query) {
  console.log('Searching for:', query);
  // 发送搜索请求
  // fetch(`https://api.example.com/search?q=${query}`)
  //   .then(response => response.json())
  //   .then(data => console.log(data));
}

// 创建一个防抖函数
const debouncedSearch = debounce(search, 300);

// 绑定到输入事件
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', function(event) {
  debouncedSearch(event.target.value);
});

使用立即执行选项

javascript
// 定义一个登录函数
function login(username, password) {
  console.log('Logging in:', username);
  // 发送登录请求
  // fetch('https://api.example.com/login', {
  //   method: 'POST',
  //   headers: {
  //     'Content-Type': 'application/json'
  //   },
  //   body: JSON.stringify({ username, password })
  // })
  //   .then(response => response.json())
  //   .then(data => console.log(data));
}

// 创建一个立即执行的防抖函数
const debouncedLogin = debounce(login, 300, true);

// 绑定到登录按钮点击事件
const loginButton = document.getElementById('login');
loginButton.addEventListener('click', function() {
  const username = document.getElementById('username').value;
  const password = document.getElementById('password').value;
  debouncedLogin(username, password);
});

使用取消功能

javascript
// 定义一个滚动函数
function handleScroll() {
  console.log('Scrolling');
  // 执行滚动相关的操作
}

// 创建一个带取消功能的防抖函数
const debouncedScroll = debounce(handleScroll, 300);

// 绑定到滚动事件
document.addEventListener('scroll', debouncedScroll);

// 在需要时取消防抖
const cancelButton = document.getElementById('cancel');
cancelButton.addEventListener('click', function() {
  debouncedScroll.cancel();
  console.log('Debounce cancelled');
});

防抖函数的注意事项

1. this指向问题

在防抖函数中,需要保存原始函数的this指向,否则在调用原始函数时,this会指向undefined(在严格模式下)或全局对象(在非严格模式下)。

javascript
function debounce(func, delay) {
  let timer = null;
  return function() {
    const context = this; // 保存this指向
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args); // 使用apply绑定this指向
    }, delay);
  };
}

2. 参数传递问题

在防抖函数中,需要保存原始函数的参数,否则在调用原始函数时,无法传递参数。

javascript
function debounce(func, delay) {
  let timer = null;
  return function() {
    const context = this;
    const args = arguments; // 保存参数
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args); // 传递参数
    }, delay);
  };
}

3. 内存泄漏问题

在使用防抖函数时,需要注意内存泄漏问题。如果防抖函数被绑定到DOM元素的事件上,当DOM元素被移除时,需要取消防抖函数,否则防抖函数会一直存在于内存中。

javascript
// 绑定防抖函数
const debouncedScroll = debounce(handleScroll, 300);
document.addEventListener('scroll', debouncedScroll);

// 当DOM元素被移除时,取消防抖函数
document.removeEventListener('scroll', debouncedScroll);
debouncedScroll.cancel();

防抖函数与节流函数的区别

防抖函数和节流函数都是用于减少函数调用次数的技术,但它们的实现原理和应用场景有所不同:

特性防抖函数节流函数
核心思想在事件触发后的一段时间内,只执行最后一次函数调用在事件触发的过程中,每隔一段时间执行一次函数调用
执行时机事件触发后等待一段时间再执行,若期间有新事件则重新计时事件触发后立即执行,然后在一段时间内不再执行
应用场景搜索框输入、表单验证、窗口大小调整等滚动事件、鼠标移动事件、游戏中的动画效果等

面试常见问题

1. 什么是防抖?它的应用场景是什么?

  • 防抖的定义:防抖是一种性能优化技术,在事件触发后的一段时间内,只执行最后一次函数调用,如果在这段时间内又触发了事件,则重新计时。
  • 应用场景:搜索框输入、表单验证、窗口大小调整、滚动事件等。

2. 如何实现一个防抖函数?

防抖函数的基本实现思路是:使用一个定时器,在事件触发时启动定时器,如果在定时器到期前又触发了事件,则清除之前的定时器,重新启动一个新的定时器。当定时器到期时,执行回调函数。

3. 防抖函数中的this指向问题如何解决?

在防抖函数中,需要保存原始函数的this指向,然后在调用原始函数时使用applycall绑定this指向。

4. 防抖函数与节流函数的区别是什么?

  • 核心思想:防抖函数在事件触发后的一段时间内只执行最后一次函数调用;节流函数在事件触发的过程中每隔一段时间执行一次函数调用。
  • 执行时机:防抖函数在事件触发后等待一段时间再执行,若期间有新事件则重新计时;节流函数在事件触发后立即执行,然后在一段时间内不再执行。
  • 应用场景:防抖函数适用于搜索框输入、表单验证等场景;节流函数适用于滚动事件、鼠标移动事件等场景。

5. 如何为防抖函数添加立即执行选项?

可以添加一个immediate选项,当immediatetrue时,在事件触发后立即执行一次函数,然后在一段时间内不再执行。

6. 如何为防抖函数添加取消功能?

可以在返回的函数上添加一个cancel方法,用于清除定时器,取消防抖函数的执行。

总结

防抖函数是一种常用的性能优化技术,它可以减少函数的调用次数,提高代码的执行效率。防抖函数的核心思想是:在事件触发后的一段时间内,只执行最后一次函数调用,如果在这段时间内又触发了事件,则重新计时。防抖函数在搜索框输入、表单验证、窗口大小调整、滚动事件等场景中特别有用。

实现防抖函数时,需要注意以下几点:

  1. 保存原始函数的this指向,避免this指向错误
  2. 保存原始函数的参数,避免参数丢失
  3. 处理内存泄漏问题,在不需要时取消防抖函数
  4. 可以添加立即执行选项和取消功能,提高函数的灵活性

防抖函数和节流函数都是用于减少函数调用次数的技术,但它们的实现原理和应用场景有所不同,需要根据具体场景选择合适的技术。

好好学习,天天向上