防抖和节流

388 阅读3分钟

1. 使用场景

scroll 事件,resize 事件、鼠标事件(比如 mousemove、mouseover 等)、键盘事件(keyup、keydown 等)都存在被频繁触发的风险。 频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。

2. “节流”与“防抖”的本质

这两个东西都以闭包的形式存在。它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。

  • 防抖:动态绑定事件,动作发生后一定时间后触发事件,在这段时间内,如果该动作又发生,则重新等待一定时间再触发事件。使用场景有输入验证
    // 防抖
  function debounce(fn) {
    let timeout = null; // 创建一个标记用来存放定时器的返回值
    return function () {
      clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
      timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
        fn.apply(this, arguments);
      }, 500);
    };
  }
  • 节流: 动态绑定事件,动作发生后一段时间后触发事件,在这段时间内,如果动作又发生,则无视该动作,直到事件执行完后,才能重新触发。使用场景有60s后才能重新点击发送验证码
    // 节流
      function throttle(fn) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
      if (!canRun) return; // 在函数开头判断标记是否为true,不为truereturn
      canRun = false; // 立即设置为false
      setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
        fn.apply(this, arguments);
        // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
        canRun = true;
      }, 500);
    };
  }

3. “节流”与“防抖”的指令实现

<!--template模板-->
<input type="text" v-model="inputContent" v-debounce="{'callback':getInput, time: '500'}"/>
// <input type="text" v-model="inputContent" v-throttle="{'callback':getInput, time: '500'}"/>
<!--js定义-->
data() {
    return: {
        inputContent: ''
    }
}
methods: {
    getInput() {
        console.log(this.inputContent, '-----')
    }
}

全局指令:

import Vue from 'vue'
// 注册
Vue.directive('throttle', {
    // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    bind: function () {},
    // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
    inserted: function (el, binding) {
      const { callback, time } = binding.value
      el.callback = callback
      el.time = time
      el.addEventListener('click', () => {
        const nowTime = + new Date()
        if (!el.preTime || nowTime - el.preTime > el.time) {
          el.preTime = nowTime
          el.callback()
        }
      })
    },
    // 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode更新之前。
    // 指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
    update: function (el, binding) {
      console.log('update')
      const { callback, time } = binding.value
      el.callback = callback
      el.time = time
    },
    // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    componentUpdated: function () {},
    // 只调用一次,指令与元素解绑时调用。
    unbind: function () {}
  }),
  
  Vue.directive('debounce', {
    bind: function (el, binding, vnode) {
    },
    inserted: function (el, binding) {
      const { callback, time } = binding.value
      el.callback = callback
      el.time = time
      el.timeCall = null
      el.addEventListener('keyup', () => {
        clearTimeout(el.timeCall)
        el.timeCall = setTimeout( () => {
          el.callback()
        }, el.time || 500) 
      })
    },
    update: function (el, binding) {
      console.log('update')
      const { callback, time } = binding.value
      el.callback = callback
      el.time = time
    },
    componentUpdated: function () {},
    unbind: function () {}
  })

4. “节流”与“防抖”的实现后的效果,点击链接可看

p1-jj.byteimg.com/tos-cn-i-t2…