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次
相关推荐
品尚公益团队10 小时前
C#在asp.net网页开发中的带cookie登录实现
前端·c#·asp.net
多加点辣也没关系10 小时前
JavaScript|第9章:对象 — 基础
开发语言·javascript·ecmascript
没有了遇见11 小时前
AI Agent 是什么?—— 一文理解 LLM、Memory、Skills、Tools、MCP、Workflow,Context
android·前端·程序员
后台开发者Ethan11 小时前
setState组件更新
前端·javascript·react
谙忆102411 小时前
大文件上传实战:前端分片、断点续传、秒传与并发控制怎么落地
前端
轻动琴弦12 小时前
VoltTool —— 一站式在线开发者工具平台,69+实用工具免费使用
前端·chrome
就叫飞六吧12 小时前
不同站点iframe嵌套种cookie、localstorege.set() 关系
前端
星栈12 小时前
LiveView 怎么测:从组件测试到端到端,我终于不再只敢手点页面了
前端·前端框架·elixir
蜡台12 小时前
uniapp vue2 转 vue3
前端·javascript·uni-app·vue3·vue2
Web4Browser12 小时前
Headless 浏览器自动化适合哪些任务?巡检、截图和提交动作要分级
运维·前端·自动化