前端进阶之防抖与节流是什么?

  • 做者:陈大鱼头
  • github: KRISACHAN
  • 连接:github.com/YvetteLau/S…
  • 背景:最近高级前端工程师 刘小夕github 上开了个每一个工做日布一个前端相关题的 repo,怀着学习的心态我也参与其中,如下为个人回答,若是有不对的地方,很是欢迎各位指出。

防抖(debounce)

防抖是什么?

防抖是什么?防抖就是防止抖动,例如小朋友喜欢多动,停不下来,而后作家长的打一顿,让小朋友安静下来,这种行为就叫作防抖。javascript

(~o ̄3 ̄)~html

biu

抖个机灵,哈哈哈~前端

抖动是一个很常见的控制函数在必定时间内执行多少次的技巧,其实就是肯定了函数执行的最小间隔,若是还在这个间隔内触发函数,则从新计算。java

举个栗子~git

你家有七个儿子,大儿子叫大娃,二儿子叫二娃,三儿子叫三娃,四儿子叫四娃,五儿子叫五娃,六儿子叫六娃,七儿子叫七娃。因为七个孩子还小,他们的衣服你只能用手洗,一洗洗七套,一次要一个小时。若是洗到一半,你的七个孩子给你过来捣乱把衣服弄脏,那么你又得花一个小时去洗,不捣乱,那么你一个小时以后你的孩子衣服就又脏了,又能够再花一个小时来洗衣服了。github

具体实现

鱼头注:如下代码来自优秀的JS库 lodash微信

// 判断是否是个对象
function isObject(value) {
  const type = typeof value
  return value != null && (type == 'object' || type == 'function')
}

/* * 建立一个 debounced 函数,延迟调用 func 直到 wait 以后 * cancel 方法用于取消 debounced * flush 方法用于当即调用 */

function debounce(func, wait, options) {
  let lastArgs,
    lastThis,
    maxWait,
    result,
    timerId,
    lastCallTime

  let lastInvokeTime = 0
  let leading = false
  let maxing = false
  let trailing = true

  // 绕过 requestAnimationFrame ,显式地设置 wait = 0
  const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')

  if (typeof func !== 'function') {
    throw new TypeError('Expected a function')
  }
  wait = +wait || 0
  if (isObject(options)) {
    leading = !!options.leading
    maxing = 'maxWait' in options
    maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
    trailing = 'trailing' in options ? !!options.trailing : trailing
  }

  function invokeFunc(time) {
    const args = lastArgs
    const thisArg = lastThis

    lastArgs = lastThis = undefined
    lastInvokeTime = time
    result = func.apply(thisArg, args)
    return result
  }

  function startTimer(pendingFunc, wait) {
    if (useRAF) {
      root.cancelAnimationFrame(timerId);
      return root.requestAnimationFrame(pendingFunc)
    }
    return setTimeout(pendingFunc, wait)
  }

  function cancelTimer(id) {
    if (useRAF) {
      return root.cancelAnimationFrame(id)
    }
    clearTimeout(id)
  }

  function leadingEdge(time) {
    // 重置任何 最大等待时间的计时器
    lastInvokeTime = time
    // 启动计时器
    timerId = startTimer(timerExpired, wait)
    // 返回调用结果
    return leading ? invokeFunc(time) : result
  }

  function remainingWait(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeSinceLastInvoke = time - lastInvokeTime
    const timeWaiting = wait - timeSinceLastCall

    return maxing
      ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
      : timeWaiting
  }

  function shouldInvoke(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeSinceLastInvoke = time - lastInvokeTime

    // 判断是否应该调用函数
    return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
      (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
  }

  function timerExpired() {
    const time = Date.now()
    if (shouldInvoke(time)) {
      return trailingEdge(time)
    }
    // 重置定时器
    timerId = startTimer(timerExpired, remainingWait(time))
  }

  function trailingEdge(time) {
    timerId = undefined

    // 若是我有最后一个参数,就意味着函数须要调用
    // 至少执行一次防抖
    if (trailing && lastArgs) {
      return invokeFunc(time)
    }
    lastArgs = lastThis = undefined
    return result
  }

  function cancel() {
    if (timerId !== undefined) {
      cancelTimer(timerId)
    }
    lastInvokeTime = 0
    lastArgs = lastCallTime = lastThis = timerId = undefined
  }

  function flush() {
    return timerId === undefined ? result : trailingEdge(Date.now())
  }

  function pending() {
    return timerId !== undefined
  }

  function debounced(...args) {
    const time = Date.now()
    const isInvoking = shouldInvoke(time)

    lastArgs = args
    lastThis = this
    lastCallTime = time

    if (isInvoking) {
      if (timerId === undefined) {
        return leadingEdge(lastCallTime)
      }
      if (maxing) {
        // 处理循环调用
        timerId = startTimer(timerExpired, wait)
        return invokeFunc(lastCallTime)
      }
    }
    if (timerId === undefined) {
      timerId = startTimer(timerExpired, wait)
    }
    return result
  }
  debounced.cancel = cancel
  debounced.flush = flush
  debounced.pending = pending
  return debounced
}
复制代码

使用方法以下:前端工程师

var 打印一些东西啦 = function (event) {
	console.log(event);
};

document.querySelector('html').addEventListener('click', debounce(打印一些东西啦, 1000));
复制代码

适用场景

如下就举几个咱们平常开发中可能会用上 debounce 的栗子:app

  • 输入框搜索,当用户中止搜索一段时间以后再执行请求操做,防止屡次触发而给服务端带来压力
  • 防止重复触发 resize 或者 scroll 事件
  • 还有吧,可是懒得动脑了~

节流(throttle)

节流是什么?

节流: 节流就是流体在管道内流动,忽然遇到截面变窄,而使压力降低的现象。节制流入或流出,尤指用节流阀调节。函数

出处:语出《荀子·富国》:“百姓时和、事业得叙者,货之源也; 等赋府库者,货之流也。故明主必谨养其和,节其流,开其源,而时斟酌焉,潢然使天下必有馀而上不忧不足。”后以“开源节流”指开辟财源,节约开支。

其实做用跟定义就是上面的意思,在平常开发中,咱们常常会有频繁调用或执行某一函数的需求,其实按实际用户状况,可能 100ms 甚至 1s 调用一次就OK,因此须要用到函数节流,在必定的时间间隔以后再执行函数。

使用场景

场景不少,常见的有:

  1. 屏幕尺寸变化时页面内容的变更,执行相应逻辑;

  2. 监听鼠标滚动时间,执行相应逻辑;

  3. 监听重复点击时的时间,执行相应逻辑

具体实现

下面依然是 lodash 的代码实现:

function isObject(value) {
  const type = typeof value
  return value != null && (type == 'object' || type == 'function')
}

function throttle(func, wait, options) {
  let leading = true
  let trailing = true

  if (typeof func !== 'function') {
    throw new TypeError('Expected a function')
  }
  if (isObject(options)) {
    leading = 'leading' in options ? !!options.leading : leading
    trailing = 'trailing' in options ? !!options.trailing : trailing
  }
  return debounce(func, wait, {
    leading,
    trailing,
    'maxWait': wait,
  })
}

复制代码

使用方法以下:

var 打印一些东西啦 = function (event) {
	console.log(event);
};

document.querySelector('html').addEventListener('click', throttle(打印一些东西啦, 1000));
复制代码


若是你、喜欢探讨技术,或者对本文有任何的意见或建议,你能够扫描下方二维码,关注微信公众号“ 鱼头的Web海洋”,随时与鱼头互动。欢迎!衷心但愿能够碰见你。

相关文章
相关标签/搜索