针对面试高频考点,面试官要你手写防抖节流,别再怕了

102 阅读6分钟

在近期的面试中,我发现许多面试官都会考大家防抖节流这一知识点,两者有什么区别,并且会让大家手写,而大部分同学看到手写这一环节就会卡壳。 对此本文将详细介绍防抖和节流的概念、实现原理、应用场景,并通过简洁的代码示例,教你如何手写防抖和节流函数。

一、为什么需要防抖与节流

(一)防抖(Debounce)

防抖是一种在事件被触发后延迟一定时间才执行回调函数的技术。如果在延迟时间内事件再次被触发,则重新计时。防抖的核心思想是“在一定时间内,只执行一次事件处理函数”。

8918b5187d788990b669941575addcb3.jpg 我们抢演唱会门票的时候, 对于抢票这个按钮我们回点很多次, 我们每点一次,服务器就会去发动一个请求, 这样就会造成服务器的压力崩溃, 而防抖就是单位时间内,只执行一次事件, 防抖的实现原理就是, 当事件触发了,我们就会等待一段时间, 在这段时间内,如果事件没有触发,我们就会执行事件, 否则,我们就会等待下一次事件触发。

应用场景:

  1. 输入框搜索:用户在输入框中输入时,通常会触发搜索请求。如果用户每次输入一个字符都发送请求,这将导致大量的网络请求,增加服务器的负担。使用防抖可以确保用户停止输入一段时间后才触发搜索请求,减少不必要的请求。
  2. 按钮点击:在用户快速点击按钮时,如果不进行限制,可能会导致多次提交数据。使用防抖可以确保按钮点击事件只触发一次,避免重复提交。
  3. 窗口调整大小:当用户调整窗口大小时,会频繁触发resize事件。使用防抖可以减少事件的触发频率,避免频繁的布局重绘。

实现原理:

防抖的实现原理是通过设置定时器来延迟执行回调函数。每次事件触发时,清除之前的定时器并重新设置一个新的定时器。如果在定时器的延迟时间内没有新的事件触发,则执行回调函数。

(二)节流(Throttle)

节流是一种在一定时间间隔内最多执行一次回调函数的技术。如果在该时间间隔内事件被多次触发,只有第一次或最后一次触发会生效。节流的核心思想是“在一定时间间隔内,最多执行一次事件处理函数”。

为什么需要节流? 防抖一直按着不动就不会触发 如果有人按着滚动条不动,就不会执行,这样也会导致性能损耗。

应用场景:

  1. 页面滚动:当用户滚动页面时,会频繁触发scroll事件。使用节流可以控制滚动事件的触发频率,减少处理次数,提高页面流畅度。
  2. 鼠标移动:当用户移动鼠标时,会频繁触发mousemove事件。使用节流可以限制鼠标移动事件的触发频率,避免触发过多的事件处理逻辑。
  3. 窗口调整大小:与防抖类似,节流也可以用于控制resize事件的触发频率,减少布局重绘。

实现原理:

节流的实现原理有两种常见的方法:

  1. 时间戳法:记录上一次执行的时间戳,每次事件触发时,检查当前时间与上一次执行的时间戳的差值是否大于设定的时间间隔。如果大于,则执行回调函数并更新时间戳。
  2. 定时器法:设置一个标志位,表示是否可以执行回调函数。在指定时间内,如果标志位未被设置,则执行函数并设置标志位。使用setTimeout来清除标志位,确保在指定时间后可以再次执行函数。

二、简洁的手写防抖与节流

(一)简洁的防抖实现

function debounce(fn, delay) {
  let timer = null; // 借助闭包
  return function(...args) {
    const context = this;
    clearTimeout(timer); // 清除之前的定时器
    timer = setTimeout(() => {
      fn.apply(context, args); // 执行回调函数
    }, delay);
  };
}

// 使用示例:输入框搜索
const inputHandler = debounce(() => {
  console.log('搜索请求已发送');
}, 500);

document.getElementById('searchInput').addEventListener('input', inputHandler);

(二)简洁的节流实现

function throttle(fn, delay) {
  let valid = true; // 标志位
  return function(...args) {
    const context = this;
    if (!valid) {
      return false; // 如果标志位为false,直接返回
    }
    valid = false; // 设置标志位为false
    setTimeout(() => {
      fn.apply(context, args); // 执行回调函数
      valid = true; // 清除标志位
    }, delay);
  };
}

// 使用示例:页面滚动
const scrollHandler = throttle(() => {
  const scrollTop = document.documentElement.scrollTop;
  console.log('页面滚动了', scrollTop);
}, 2000);

window.onscroll = scrollHandler;

三、详细代码解析

(一)防抖代码解析

function debounce(fn, delay) {
  let timer = null; // 借助闭包,存储定时器的ID
  return function(...args) {
    const context = this; // 保存this的上下文
    clearTimeout(timer); // 清除之前的定时器
    timer = setTimeout(() => {
      fn.apply(context, args); // 执行回调函数
    }, delay);
  };
}
  1. 闭包timer变量存储了定时器的ID,通过闭包的方式,每次返回的函数都可以访问到这个变量。
  2. 清除定时器:每次事件触发时,先清除之前的定时器,避免重复设置定时器。
  3. 延迟执行:使用setTimeout来延迟执行回调函数。如果在延迟时间内没有新的事件触发,则执行回调函数。

(二)节流代码解析

function throttle(fn, delay) {
  let valid = true; // 标志位,表示是否可以执行回调函数
  return function(...args) {
    const context = this; // 保存this的上下文
    if (!valid) {
      return false; // 如果标志位为false,直接返回
    }
    valid = false; // 设置标志位为false
    setTimeout(() => {
      fn.apply(context, args); // 执行回调函数
      valid = true; // 清除标志位
    }, delay);
  };
}
  1. 标志位valid变量用于控制是否可以执行回调函数。如果validfalse,则表示当前正在等待,直接返回。
  2. 延迟执行:使用setTimeout来延迟执行回调函数。在指定时间间隔后,执行回调函数并清除标志位,允许下一次执行。

四、应用场景与优化

(一)输入框搜索

const inputHandler = debounce(() => {
  console.log('搜索请求已发送');
}, 500);

document.getElementById('searchInput').addEventListener('input', inputHandler);
  • 优化:在实际应用中,可以结合防抖和节流,根据输入频率动态调整延迟时间,进一步优化性能。

(二)页面滚动

const scrollHandler = throttle(() => {
  const scrollTop = document.documentElement.scrollTop;
  console.log('页面滚动了', scrollTop);
}, 2000);

window.onscroll = scrollHandler;
  • 优化:对于滚动事件,可以使用时间戳法来实现节流,减少延迟时间的固定限制,使滚动更加流畅。

五、总结

防抖和节流是前端开发中常用的性能优化手段,它们可以帮助我们有效减少事件处理函数的执行次数,提升性能和用户体验。通过本文的介绍,你已经了解了防抖和节流的概念、实现原理、应用场景,并学会了如何简洁地手写防抖和节流函数。在实际开发中,可以根据具体需求选择合适的优化策略,进一步提升应用的性能和稳定性。

希望本文对你有所帮助,如果你有任何疑问或建议,欢迎在评论区留言交流。