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次
相关推荐
o***Z44812 小时前
前端性能优化案例
前端
张拭心12 小时前
前端没有实际的必要了?结合今年工作内容,谈谈我的看法
前端·ai编程
姜太小白12 小时前
【前端】CSS媒体查询响应式设计详解:@media (max-width: 600px) {……}
前端·css·媒体
weixin_4111918413 小时前
flutter中WebView的使用及JavaScript桥接的问题记录
javascript·flutter
HIT_Weston13 小时前
39、【Ubuntu】【远程开发】拉出内网 Web 服务:构建静态网页(二)
linux·前端·ubuntu
百***060113 小时前
SpringMVC 请求参数接收
前端·javascript·算法
天外天-亮13 小时前
Vue + excel下载 + 水印
前端·vue.js·excel
起个名字逛街玩13 小时前
前端正在走向“工程系统化”:从页面开发到复杂产品架构的深度进化
前端·架构
用户479492835691513 小时前
React 渲染两次:是 Bug 还是 Feature?聊聊严格模式的“良苦用心”
前端·react.js·前端框架
用户479492835691513 小时前
Code Review 惊魂:同事的“优雅”重构,差点让管理员全部掉线
javascript