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次
相关推荐
兮山与29 分钟前
前端1.0
前端
kfepiza2 小时前
JavaScript将String转为base64 笔记250802
开发语言·javascript·笔记
Warren982 小时前
Vue2博客项目笔记(第一天)
java·开发语言·javascript·vue.js·windows·笔记·ecmascript
王者鳜錸3 小时前
VUE+SPRINGBOOT从0-1打造前后端-前后台系统-邮箱重置密码
前端·vue.js·spring boot
独泪了无痕5 小时前
深入浅析Vue3中的生命周期钩子函数
前端·vue.js
小白白一枚1115 小时前
vue和react的框架原理
前端·vue.js·react.js
字节逆旅5 小时前
从一次爬坑看前端的出路
前端·后端·程序员
若梦plus6 小时前
微前端之样式隔离、JS隔离、公共依赖、路由状态更新、通信方式对比
前端
若梦plus6 小时前
Babel中微内核&插件化思想的应用
前端·babel
若梦plus6 小时前
微前端中微内核&插件化思想的应用
前端