Javascript面试重点-手写防抖节流函数

手写防抖节流函数

在前面的学习过程中, 我们对于防抖和节流是非常清晰的了, 但是我们是使用的第三方库实现的防抖和节流

  • 在实际开发中我们都是使用第三方库的防抖和节流, 但是我们手写防抖和节流会有助于阅读源码, 了解底层

  • 接下来我们实现一下自己的防抖函数和节流函数

1. 手写防抖函数

我们按照如下思路来实现

  • 防抖基本功能实现:可以实现防抖效果(掌握重点)
  • 优化一:优化参数和this指向(了解)
  • 优化二:优化取消操作(增加取消功能)(了解)
  • 优化三:优化立即执行效果(第一次立即执行)(了解)
1.1 基本实现

当我们准备实现一个工具函数时, 按照以下步骤:

  1. 需要接收什么参数
  2. 返回值是什么
  3. 内部实现

1.需要接收什么参数:

  • 参数一: 回调的函数

  • 参数二: 延迟时间

    // 1.需要接收两个参数
    function mydebounce(fn, delay) {
      
    }
    

2.返回值是什么

  • 最终我们返回结果是要绑定对应的监听事件的, 所以返回值一定是一个新的函数

    // 1.需要接收两个参数
    function mydebounce(fn, delay) {
      const _debounce = () => {}
      // 2.返回一个新的函数
      return _debounce
    }
    

3.内部实现

  • 我们可以在_debounce函数中开启个定时器, 定时器的延迟时间就使用delay

  • 并且每开启一个定时器, 我们都需要将上一次的定时器取消掉

    function mydebounce(fn, delay) {
      // 1.创建一个变量, 用于记录上一次定时器
      let timer = null
    
      // 2.触发事件时执行的函数
      const _debounce = () => {
        // 2.1第一次timer为null, 所有我们需要判断timer有值时清除定时器
        if (timer) clearTimeout(timer)
    
        // 2.2延迟执行传入的fn回调
        timer = setTimeout(() => {
          fn()
          // 2.3函数执行完成后, 我们需要将timer重置
          timer = null
        }, delay)
      }
      // 返回一个新的函数
      return _debounce
    }
    

这样防抖的基本实现就已经实现完成, 已经可以实现防抖的效果, 我们可使用上一篇的输入框案例测试一下

  • 测试代码帮大家拿过来

    <input type="text" />
    <!-- 引用本地js文件 -->
    <script src="./3.手写防抖.js"></script>
    <script>
      const inputEl = document.querySelector("input");
    
      let counter = 1;
      function searchChange() {
        console.log(`${counter++}次网络请求`, this.value);
      }
    
      // 使用自己封装的函数实现防抖
      inputEl.oninput = mydebounce(searchChange, 3000);
    </script>
    

在这里插入图片描述

完成以上代码, 我们已经实现了防抖函数最核心的一部分代码, 在面试中能写出以上代码已经可以了, 接下来我们就是对防抖函数的功能进行完善和优化

1.2 优化this指向和参数

在上面测试中我们发现, value打印的值是undefined

  • 这是因为我们在代码中直接调用了fn(), 我们知道独立函数调用this指向window, 而window中没有value这个值, 因此打印undefined
  • 我们需要让this指向事件的调用者

优化this

  • 我们可以使用显式绑定apply方法, 并且将_debonce的箭头函数改为普通函数

  • _debonce修改为普通函数, _debonce中的this会指向事件调用者

    function mydebounce(fn, delay) {
      let timer = null
    
      // 1.箭头函数不绑定this, 修改为普通函数
      const _debounce = function() {
        if (timer) clearTimeout(timer)
    
        timer = setTimeout(() => {
          // 2.使用显式绑定apply方法
          fn.apply(this)
          timer = null
        }, delay)
      }
      // 返回一个新的函数
      return _debounce
    }
    

优化参数

  • 在事件监听的函数我们是有可能传入一些参数的, 例如event等

    function mydebounce(fn, delay) {
      let timer = null
    
      // 1.接收可能传入的函数
      const _debounce = function(...args) {
        if (timer) clearTimeout(timer)
    
        timer = setTimeout(() => {
          // 2.再将参数传给fn
          fn.apply(this, args)
          timer = null
        }, delay)
      }
      // 返回一个新的函数
      return _debounce
    }
    

再进行测试

  • 测试代码

    <input type="text" />
    <!-- 引用本地js文件 -->
    <script src="./3.手写防抖.js"></script>
    <script>
      const inputEl = document.querySelector("input");
    
      let counter = 1;
    	// 传入event参数并打印进行测试
      function searchChange(event) {
        console.log(`${counter++}次网络请求`, this.value, event);
      }
    
      // 使用自己封装的函数实现防抖
      inputEl.oninput = mydebounce(searchChange, 3000);
    </script>
    

在这里插入图片描述

1.3 优化增加取消操作

什么是取消操作呢?

  • 给大家解释一下, 例如用户在表单输入的过程中
  • 返回了上一层页面或者关闭了页面, 就意味着我们这次延迟的网络请求没有必要继续发生了
  • 我们应该提供这样一个可取消发送请求的功能

优化取消操作

  • 给_debounce添加一个取消的方法

    function mydebounce(fn, delay) {
      let timer = null
    
      const _debounce = function(...args) {
        if (timer) clearTimeout(timer)
    
        timer = setTimeout(() => {
          fn.apply(this, args)
          timer = null
        }, delay)
      }
    
      // 给_debounce添加一个取消函数
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
      }
    
      // 返回一个新的函数
      return _debounce
    }
    
  • 测试代码

    <input type="text" />
    <button>取消</button>
    
    <!-- 引用本地js文件 -->
    <script src="./3.手写防抖.js"></script>
    <script>
      const inputEl = document.querySelector("input");
      const btnEl = document.querySelector("button");
    
      let counter = 1;
      function searchChange() {
        console.log(`${counter++}次网络请求`, this.value);
      }
    
      const debonceFn = mydebounce(searchChange, 3000);
    
      // 使用自己封装的函数实现防抖
      inputEl.oninput = debonceFn;
    
      // 测试取消功能
      btnEl.onclick = function () {
        debonceFn.cancel();
      };
    </script>
    

在这里插入图片描述

1.4 优化增加立即执行

增加立即执行功能:

  • 有些场景需要第一次输入时, 立即执行, 后面的输入再使用防抖延迟执行
  • 我们作为框架开发者, 需要提供这样一个功能, 而这个功能不是所有人都需要的

增加立即执行功能

  • 我们可以提供第三个参数, 如果需要此功能, 那么传入第三个参数

    // 1.设置第三个参数, 并且默认值为false
    function mydebounce(fn, delay, immediate = false) {
      let timer = null
      // 2.定义变量, 用于记录状态
      let isInvoke = false
    
      const _debounce = function(...args) {
        if (timer) clearTimeout(timer)
    
        // 3.第一次执行不需要延迟
        if (!isInvoke && immediate) {
          fn.apply(this, args)
          isInvoke = true
          return
        }
    
        timer = setTimeout(() => {
          fn.apply(this, args)
          timer = null
          // 4.重置isInvoke
          isInvoke = false
        }, delay)
      }
    
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
        // 取消也需要重置
        timer = null
        isInvoke = false
      }
    
      // 返回一个新的函数
      return _debounce
    }
    
  • 我们将第三个参数传为true测试一下吧

    <input type="text" />
    <button>取消</button>
    <!-- 引用本地js文件 -->
    <script src="./3.手写防抖.js"></script>
    <script>
      const inputEl = document.querySelector("input");
      const btnEl = document.querySelector("button");
    
      let counter = 1;
      function searchChange() {
        console.log(`${counter++}次网络请求`, this.value);
      }
    
    	// 传入第三个参数为true开启立即执行功能
      const debonceFn = mydebounce(searchChange, 3000, true);
    
      inputEl.oninput = debonceFn;
    
      btnEl.onclick = function () {
        debonceFn.cancel();
      };
    </script>
    

在这里插入图片描述

2. 手写节流函数

我们前面有使用第三方库的节流函数

接下来手写实现一下自己的节流函数吧

我们按照如下思路来实现

  • 节流函数的基本实现:可以实现节流效果(掌握重点)
  • 优化一:节流最后一次也可以执行(了解)
  • 优化二:优化添加取消功能(了解)
  • 优化三:优化返回值问题(了解)
2.1 基本实现

实现一个工具函数时, 应按照以下步骤:

  1. 需要接收什么参数
  2. 返回值是什么
  3. 内部实现

1.需要接收什么参数

需要接收两个参数:

  • 参数一: 要执行的回调函数

  • 参数二: 要执行的间隔时间

    // 1.需要接收两个参数, 第一个参数要执行的回调, 第二个参数间隔时间
    function mythrottle(fn, interval) {
    
    }
    

2.返回值是什么

  • 最终我们返回结果是要绑定对应的监听事件的, 所以返回值一定是一个新的函数

    // 1.需要传入两个参数, 第一个参数要执行的回调, 第二个参数间隔时间
    function mythrottle(fn, interval) {
      const _throttle = function() {
    
      }
    
      // 2. 返回一个新的函数
      return _throttle
    }
    

3.内部实现

思路: 实现节流函数, 我们使用定时器是不方便管理的, 实现节流函数我们采用另一个思路

  • 我们获取一个当前时间nowTime, 我们使用new Date().gettime()方法获取, 在设定一个开始时间startTime, 等待时间waitTime

  • waitTime = interval - (nowTime - startTime), 当前的时间减去开始的时间得到结果, 再使用间隔时间减去这个结果, 就可以得到等待时间

  • 得到等待时间我们在进行判断, 如果等待时间小于等于0, 那么就可以执行回调函数

  • 开始时间startTime我们初始值为0就好, 当第一次执行时, nowTime获取的时间戳是一个非常大的值, 得到的结果waitTime是负值, 所以第一次执行节流函数, 一定会立即执行, 这也符合我们要封装的效果

    function mythrottle(fn, interval) {
      // 1.定义变量保记录开始时间
      let startTime = 0
    
      const _throttle = function() {
        // 2. 获取当前时间
        const nowTime = new Date().getTime()
        // 3.计算需要等待的时间
        const waitTime = interval - (nowTime - startTime)
    
        // 4.当等待的时间小于等于0时, 执行回调函数 
        if (waitTime <= 0) {
          fn()
          // 并让开始时间等于现在时间 
          startTime = nowTime
        }
      }
    
      return _throttle
    }
    

节流函数的基本功能就实现了, 我们同样使用前的输入框案例测试一下

  • 测试代码帮大家拿过来了

    <input type="text" />
    <!-- 引入自己的节流函数 -->
    <script src="./4.手写节流.js"></script>
    <script>
      const inputEl = document.querySelector("input");
    
      let counter = 1;
      function searchChange() {
        console.log(`${counter++}次网络请求`, this.value);
      }
    
    
      // 实现节流
      inputEl.oninput = mythrottle(searchChange, 500);
    </script>
    

在这里插入图片描述

完成上面代码, 我们就已经掌握节流函数最核心的代码了, 在面试中写出上面代码即可, 重点掌握上面代码, 接下来是对节流函数的优化和功能的完善(了解了解即可)

2.2 优化this指向和参数

相信大家知道, 打印undefined是因为this指向问题, 刚刚手写防抖函数相信已经知道如何优化了

  • 下面我们优化一下this指向
  • 以及接收参数, 这里思路和防抖是一样的
function mythrottle(fn, interval) {
  let startTime = 0

  const _throttle = function(...args) {
    const nowTime = new Date().getTime()
    const waitTime = interval - (nowTime - startTime)

    if (waitTime <= 0) {
      // 只需要显式绑定this
      fn.apply(this, arg)
      startTime = nowTime
    }
  }

  return _throttle
}
  • 再进行测试

在这里插入图片描述

2.3 控制立即执行

前面我们有提到, 节流函数第一次会立即执行, 不用等待

  • 但是我们第一次执行目前是不可控的
  • 作为开发者, 我们需要考虑使用的人, 是否需要这个功能
  • 因此我们需要对这个第一次的立即执行进行控制
  • 我们可以给用户提供第三个参数, 用于控制是否立即执行

    // 1.设置第三个参数控制是否默认执行, 默认为true
    function mythrottle(fn, interval, immedoate = true) {
      let startTime = 0
    
      const _throttle = function() {
        const nowTime = new Date().getTime()
        
        // 2.控制是否立即执行
        if (!immedoate && startTime === 0) {
          startTime = nowTime
        }
    
        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          fn.apply(this)
          startTime = nowTime
        }
      }
    
      return _throttle
    }
    
  • 测试代码

    <input type="text" />
    <!-- 引入自己的节流函数 -->
    <script src="./4.手写节流.js"></script>
    <script>
      const inputEl = document.querySelector("input");
    
      let counter = 1;
      function searchChange() {
        console.log(`${counter++}次网络请求`, this.value);
      }
    
    
      // 传入第三个参数false进行测试
      inputEl.oninput = mythrottle(searchChange, 500, false);
    </script>
    

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学全栈的灌汤包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值