Skip to content
This repository has been archived by the owner on Feb 9, 2021. It is now read-only.

函数防抖与节流 #10

Open
lbwa opened this issue Apr 18, 2018 · 1 comment
Open

函数防抖与节流 #10

lbwa opened this issue Apr 18, 2018 · 1 comment
Labels
Code skill Skill from coding optimization optimized solutions Pure JS Pure JavaScript .etc

Comments

@lbwa
Copy link
Owner

lbwa commented Apr 18, 2018

函数防抖

总结
意为在某段持续时间内,不断地触发事件,但只执行最后一次回调函数的调用。

实现:

/**
 * @param  {Function} fn     要实现函数防抖的原函数
 * @param  {Number}   delay  延迟时间
 * @return {Function}        添加防抖功能的包装函数
 */
// 最后一次调用总是会被执行
function debounce (fn, delay = 200, now = false) { // 延迟默认值 200ms
  let _timer = null // 匿名函数保持了对 _timer 变量的引用
  return function (...args) { // rest 参数,保存传入参数,用于向 fn 传递参数
    if (_timer) clearTimeout(_timer)
    // 箭头函数没有自己的 this 对象
    _timer = setTimeout(() => { // 这里的箭头函数中调用的 this 是外部匿名函数的 this
      fn.apply(this, args) // 指定 this 和参数,若不使用 apply 指定,那么 this 将指向 window
    }, delay)
  }
}

// 第一次立即调用后启用防抖
function debounce(fn, wait = 200, now = false) {
  let __timer = null
  return function (...args) {
    if (!__timer && now) {
      fn.apply(this, args)
    }
    
    if (__timer) clearTimeout(__timer)
    __timer = setTimeout(() => {
      fn.apply(this, args)
    }, wait)
  }
}

:一个易错点就是 setTimeout 中为箭头函数时,因为箭头函数自身是没有 this 对象的,它内部的 this 对象是外部的 this 对象,那么此时可直接调用匿名包装函数的 this(这也是箭头函数的一个典型应用)。但若 setTimout 中是非箭头函数时,必须先在外部引用匿名函数的 this,即 _that = this,然后再用 apply() 方法指定调用 fn 时的 this 对象。

以上示例中,debounce 函数修饰作用,用于定义一个闭包变量存储定时器和传入延迟载荷,返回的匿名函数也是 fn 函数的一个修饰,用于判断是否执行函数。其中 ...args 为 ES6 rest 参数,它定义了在调用匿名函数时,由传入的参数组成的数组(对于 arguments 伪数组而言)。

其中在 setTimeout 任务分发器中,是一个异步调用,那么必须指定调用 fn 的 this 和调用 fn 的包装匿名函数的传入参数。这是为了保证在使用防抖函数后调用 fn 与在没有使用防抖函数时调用 fn 的 this 对象和 arguments 对象一致。若不指定那么执行fn 时的 this 将指向 window,并且调用 fn 时无法正确传入 arguments 参数对象。

Vuejs 中的应用

// Vue.js 中使用函数防抖

created () {
  // watch 中真正的回调函数是 debounce() 返回的匿名函数
  this.$watch('query', debounce (newQuery => {
    this.$emit('queryChange', newQuery)
  }, 200))
}

在以上示例中,debounce() 表示了函数被调用,那么真正的回调函数是 debounce() 返回的匿名包装函数。因为 fn 的 this 与匿名包装函数的 this 是保持一致(使用 apply 指定的)的,那么 fn 的 this 此时是指向 Vue 实例组件的,rest 参数为由 newValueoldValue 组成的数组。

示例配置示例使用

函数节流

(结合 防抖函数 来实现节流函数的最后一次调用。)

意为在指定的单位时间内,只执行一次回调函数的调用。这是为了控制回调函数的一个最大的调用频率

/**
 * 用于限制 fn 函数在 period 时间段内只调用一次,即限制 fn 调用的频率
 * 示例中实现了首次和末次一定会被调用,中间调用被限定为一定频率
 *
 * @param {Function} fn 要被节流的函数
 * @param {number} [period=200] 被节流的时间段
 * @returns 一个匿名函数包装
 */
export function throttle (fn, period=200) {
  let _lastTime = null
  let _timer = null
  return function (...args) {
    const _nowTime = +new Date()

    _timer && clearTimeout(_timer)

    if (!_lastTime || _nowTime - _lastTime > period) {
      fn.apply(this, args)
      _lastTime = _nowTime
    } else {
      // 确保最后一次即使不满足 period 时间段,但仍会调用
      // 使用箭头函数来确保 this 不变
      _timer = setTimeout(() => {
        fn.apply(this, args)
      }, period)
    }
  }
}

以上示例是简单的函数节流实现,throttle() 函数作为一个函数包装器,传递目标函数和单位时间。
执行 throttle() 函数,返回的是另一个匿名包装函数,作用是限制 fn 的调用频率。判断是否是第一次执行,若不是第一次调用,则判断当前时间与上一次执行时的时间差,若大于指定的单位时间 wait ,则执行 fn 函数,使用 apply() 和 rest 参数,指定了调用 fn 时的 this 对象和 arguments 对象。并将执行时间 _lastTime 设置为当前时间 nowTime

函数防抖和函数节流对比

它们的目标都是为了防止过多的无意义函数调用。

函数防抖的目的是在多次连续触发事件时,在指定时间内(delay 参数)若再次触发调用回调函数,那么将忽略当前的函数调用,只有在经过指定时间内,没有再次触发事件时,才会真正的调用回调函数。

函数节流的目的是为了限制回调函数的最高执行频率的(比如 1s 内最多执行 2 次)。在单位时间内(wait 参数)最多执行一次回调函数调用。多余的回调函数调用将被忽略。

二者与普通回调函数调用的可视化对比如下:
debounceandthrottle

参考

函数防抖和节流

JavaScript 函数节流和函数去抖应用场景辨析

高级版函数防抖和节流

@lbwa lbwa added Pure JS Pure JavaScript .etc Code skill Skill from coding labels Apr 18, 2018
@lbwa
Copy link
Owner Author

lbwa commented Jul 21, 2018

易错点

debounce(fn, 200) 中的 fn 不推荐是箭头函数,因为箭头函数本身没有 this ,那么将导致包装函数中的指定 this 操作无效。

element.addEventListener('mouseover', debounce(() =>{
  // 借用外部 this ,即 window 对象,那么包装函数将无法绑定 this 为 element
  console.log(this) // window
}), false)

element.addEventListener('mouseover', debounce(function () {
  console.log(this) // element
}), false)

@lbwa lbwa added the optimization optimized solutions label Jul 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Code skill Skill from coding optimization optimized solutions Pure JS Pure JavaScript .etc
Projects
None yet
Development

No branches or pull requests

1 participant