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次
相关推荐
这是个栗子18 分钟前
【Vue代码分析】前端动态路由传参与可选参数标记:实现“添加/查看”模式的灵活路由配置
前端·javascript·vue.js
刘一说26 分钟前
Vue 动态路由参数丢失问题详解:为什么 `:id` 拿不到值?
前端·javascript·vue.js
熊猫钓鱼>_>1 小时前
动态网站发布部署核心问题详解
前端·nginx·容器化·网页开发·云服务器·静态部署
方也_arkling1 小时前
elementPlus按需导入配置
前端·javascript·vue.js
爱吃大芒果1 小时前
Flutter for OpenHarmony 实战: mango_shop 资源文件管理与鸿蒙适配
javascript·flutter·harmonyos
我的xiaodoujiao1 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 44--将自动化测试结果自动推送至钉钉工作群聊
前端·python·测试工具·ui·pytest
沛沛老爹1 小时前
Web开发者转型AI:多模态Agent视频分析技能开发实战
前端·人工智能·音视频
David凉宸1 小时前
vue2与vue3的差异在哪里?
前端·javascript·vue.js
Irene19911 小时前
JavaScript字符串转数字方法总结
javascript·隐式转换
笔画人生1 小时前
Cursor + 蓝耘API:用自然语言完成全栈项目开发
前端·后端