day41-“练字”-"debouncejs"

debounce

本文主要是临摹Lodashjs中debouncejs源代码。对一部分细节进行了解,代码中会附上链接并写中文注释。分为两部分:loadsh-debounce是来自官方v4.17.5文档的解释。源代码是来自官方github上的代码,日期为2019-2-20之前。


lodash-debounce

  • 背景
    _.debounce(func, [wait=0], [options={}])
  • 描述
    Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked. The debounced function comes with a cancel method to cancel delayed func invocations and a flush method to immediately invoke them. Provide options to indicate whether func should be invoked on the leading and/or trailing edge of the wait timeout. The func is invoked with the last arguments provided to the debounced function. Subsequent calls to the debounced function return the result of the last func invocation.

  • Note:
    If leading and trailing options are true, func is invoked on the trailing edge of the timeout only if the debounced function is invoked more than once during the wait timeout.
    If wait is 0 and leading is false, func invocation is deferred until to the next tick, similar to setTimeout with a timeout of 0.

  • Arguments
    func (Function): The function to debounce.
    [wait=0] (number): The number of milliseconds to delay.
    [options={}] (Object): The options object.
    [options.leading=false] (boolean): Specify invoking on the leading edge of the timeout.
    [options.maxWait] (number): The maximum time func is allowed to be delayed before it’s invoked.
    [options.trailing=true] (boolean): Specify invoking on the trailing edge of the timeout.
  • Returns
    (Function): Returns the new debounced function.

源代码-加一些解析

/**
* lodash 中的debounce实现
*/

/** 常量 */
/** lodash https://github.com/lodash/lodash/blob/master/.internal/freeGlobal.js */
/** Detect free variable `global` from Node.js. */
const freeGlobal = typeof global == 'object' && global !== null && global.Object === Object && global
/** lodash https://github.com/lodash/lodash/blob/master/.internal/root.js */
/** Detect free variable `self`. */
const freeSelf = typeof self == 'object' && self !== null && self.Object === Object && self
/** Used as a reference to the global object. */
const root = freeGlobal || freeSelf || Function('return this')()

/** 判断对象 */
function isObject(value) {
const type = typeof value
return value != null && (type == 'object' || type == 'function')
}
/** debounce */
function debounce(func, wait, options) {
let lastArgs, lastThis, maxWait, result, timerId, lastCallTime
let lastInvokeTime = 0
let leading = false
let maxing = false
let trailing = true

// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
// 注意(!wait && wait)写法,通过``明确设置wait=0
// requestAnimationFrame接受一个动画执行函数作为参数,这个函数的作用是仅执行一帧动画的渲染。回调函数会在浏览器下一次重绘之前执行
// requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧
// 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
// 调用requestAnimationFrame后,它会要求浏览器根据自己的频率进行一次重绘,它接收一个回调函数作为参数,在即将开始的浏览器重绘时,会调用这个函数,并会给这个函数传入调用回调函数时的时间作为参数。由于requestAnimationFrame的功效只是一次性的,所以若想达到动画效果,则必须连续不断的调用requestAnimationFrame
// cancelAnimationFrame 取消requestAnimationFrame的回调
// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'Function')
if (typeof func !== 'function') {
throw new TypeError('Exception a function')
}
// 一元正号将字符串转换成整数和浮点数形式,转换非字符串值 true,false 和 null,小数和十六进制格式字符串。
// 负数形式字符串也可以转换成数值(对于十六进制不适用)。如果它不能解析一个值,则计算结果为 NaN。
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(pendingFunc)
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
// 取消定时器
function cancelTimer (id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}
// 函数在每个等待时延的开始被调用
function leadingEdge (time) {
// Reset any `maxWait` timer
// 重置最长等待定时器
lastInvokeTime = time
// Start the timer for the trailing edge.
// 开启??定时器
timerId = startTimer(timerExpired, wait)
// Invoke the leading edge
// 执行
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

// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
}

function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// Restart the timer.
timerId = startTimer(timerExpired, remainingWait(time))
}

function trailingEdge(time) {
timerId = undefined

// only invoke if we have `lastArgs` which means `func has been debounced at least once
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(Data.now())
}

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

function debounce(...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) {
// Handle invocations in a tight loop.
// 多循环中处理调用
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
}

资料

附上Lodashjs文档中一篇资料来解释防抖和节流。
debouncing-throttling-explained-examples https://css-tricks.com/debouncing-throttling-explained-examples/

参考

github源代码-lodash/debounce.js
文档-_.debounce(func, [wait=0], [options={}])

文章作者: lmislm
文章链接: http://lmislm.com/2019/02/20/2019-02-20/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LMISLMのBlog