JS基础 - 防抖节流理解及手写

防抖和节流都是做性能优化的方案,它们功能相近,经常会被一起提及,但却又有不同的适用场景。

防抖

概念及手写

所谓的防抖,其实是函数的一种特性,可以描述为:

一个具有防抖特性的函数被触发时,它将等待特定时间后"执行",但如果在等待期间被再次触发,则重新计时。

我们平常说的"防抖函数",通常指的是"能返回具有防抖特性函数的函数"。也就是说所谓的防抖函数是一个高阶函数,它本身并不具有防抖的特性,而是经过它包装之后,所返回的函数具有防抖特性。

为了实现在等待时间内重复调用函数时,会重新计时的特点,需要借助闭包的特点,在父级函数中定义变量,使返回出去的闭包函数在每次运行时,都能访问到同一个变量(再去做相应的逻辑判断),而不是重新生成局部变量(这样无法在多次调用时共享状态)。

具体实现如下所示:

javascript 复制代码
function debounce(fn, delay) {
  // 在父级函数中定义,使返回的闭包函数在多次调用时,能访问到同一个变量
  let timer = null;

  return function () {
    // 每次执行函数时,清除上一次可能存在的延时器
    timer && clearTimeout(timer);

    const args = arguments;
    // 函数每次被触发时,都重新开始计时
    timer = setTimeout(() => {
      // this绑定为闭包函数运行时的this指向
      fn.apply(this, args);
    }, delay);
  };
}

上述代码能保证函数 fn 的 this 能正确绑定,原因如下:

箭头函数被声明的时间是在 setTimeout 执行的瞬间(或者说前一刻),此刻箭头函数的 this 指向会继承上层作用域的 this 指向,也就是返回出去的闭包函数的 this 指向。所以防抖函数返回出去的"真正的防抖函数"是可以按照需求绑定 this 指向的。

应用场景

在某些快速触发事件的时候,希望在整个频繁触发的过程中都不运行函数,只在最后一次触发后运行的情况,如:

表单的校验:当用户快速输入内容时,在整个输入期间都不对内容进行校验,只在停止输入后进行一次校验。

节流

概念及手写

同样的,节流也是函数的一种特性,它的特点可描述为:

一个具有节流特性的函数被触发时,它会立即执行并且开始倒计时,在倒计时结束前无法再次被触发运行。

节流函数的实现同样需要借助闭包的特性,以及注意 this 的绑定,具体代码如下所示:

javascript 复制代码
function throttle(fn, delay) {
  // 在父级函数中定义,使返回的闭包函数在多次调用时,能访问到同一个变量
  let timer = null;

  return function () {
    // 如果timer不存在,则代表倒计时结束了
    if (timer === null) {
      fn.apply(this, arguments);
      // 开始本次倒计时
      timer = setTimeout(() => {
        // 倒计时结束,重置标志位
        timer = null;
      }, delay);
    }
  };
}

应用场景

在某些快速触发事件的时候,希望减少函数真正运行的次数的情况,如:

搜索栏输入框:当用户在输入框快速输入时,每隔特定时间展示新的建议项,可减少请求次数,并且在整个输入期间都能展现建议项。

测试用例

以下是完整代码及测试用例,展示防抖及节流的特点,以及正确的传参及 this 绑定。

javascript 复制代码
function debounce(fn, delay) {
  // 在父级函数中定义,使返回的闭包函数在多次调用时,能访问到同一个变量
  let timer = null;

  return function () {
    // 每次执行函数时,清除上一次可能存在的延时器
    timer && clearTimeout(timer);

    const args = arguments;
    // 函数每次被触发时,都重新开始计时
    timer = setTimeout(() => {
      // this绑定为闭包函数运行时的this指向
      fn.apply(this, args);
    }, delay);
  };
}

function throttle(fn, delay) {
  // 在父级函数中定义,使返回的闭包函数在多次调用时,能访问到同一个变量
  let timer = null;

  return function () {
    // 如果timer不存在,则代表倒计时结束了
    if (timer === null) {
      fn.apply(this, arguments);
      // 开始本次倒计时
      timer = setTimeout(() => {
        // 倒计时结束,重置标志位
        timer = null;
      }, delay);
    }
  };
}

function print(type, x, y, z) {
  console.log(type + '打印:', x, y, z, this.text)
}
// 用于测试this指向是否绑定成功
const obj = {
  text: 'hello world'
}
const dePrint = debounce(print, 1000)
const throPrint = throttle(print, 1000)

// 防抖函数测试函数
function testDebounce() {
  let count = 0 

  let timer = setInterval(() => {
    dePrint.apply(obj, ['防抖', 1,2,3])
    count++
    // 每隔500毫秒尝试触发一次,共尝试4次
    if (count === 4) clearInterval(timer)
  }, 500)
}

// 节流函数测试函数
function testThrottle() {
  let count = 0 

  let timer = setInterval(() => {
    throPrint.apply(obj, ['节流', 1,2,3])
    count++
    // 每隔500毫秒尝试触发一次,共尝试4次
    if (count === 4) clearInterval(timer)
  }, 500)
}

testDebounce() // 最终只会在最后一次运行dePrint
testThrottle() // 最终会成功运行2次
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax