debounce.js(防抖)
root 全局对象
isObject 判断变量是否是广义的对象(对象、数组、函数), 不包括null
import isObject from './isObject.js'
import root from './.internal/root.js'
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked, or until the next browser frame is drawn. 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 the next tick, similar to `setTimeout` with a timeout of `0`.
*
* If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
* invocation will be deferred until the next frame is drawn (typically about * 16ms).
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `debounce` and `throttle`.
*
* @since 0.1.0 * @category Function
* @param {Function} func The function to debounce.
* @param {number} [wait=0]
* The number of milliseconds to delay; if omitted, `requestAnimationFrame` is
* used (if available).
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=false]
* Specify invoking on the leading edge of the timeout.
* @param {number} [options.maxWait]
* The maximum time `func` is allowed to be delayed before it's invoked.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // Avoid costly calculations while the window size is in flux.
* jQuery(window).on('resize', debounce(calculateLayout, 150))
*
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
* jQuery(element).on('click', debounce(sendMail, 300, { * 'leading': true,
* 'trailing': false * }))
*
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
* const debounced = debounce(batchLog, 250, { 'maxWait': 1000 })
* const source = new EventSource('/stream')
* jQuery(source).on('message', debounced)
*
* // Cancel the trailing debounced invocation.
* jQuery(window).on('popstate', debounced.cancel)
*
* // Check for pending invocations.
* const status = debounced.pending() ? "Pending..." : "Ready"
*/
function debounce(func, wait, options) {
let lastArgs, // 上次调用参数
lastThis, // 上次调用this
maxWait, // 允许被延迟的最大值
result, // 返回结果
timerId, // 定时器
lastCallTime // 最近调用时间
let lastInvokeTime = 0 // 上次调用func时间,即成功执行时间
let leading = false // 指定在超时前调用
let maxing = false // 是否传入等待时间
let trailing = true // 指定在超时后调用
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function') // 通过requestAnimationFrame 设置wait=0;
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) { //调用func,参数为当前时间
const args = lastArgs // 调用参数
const thisArg = lastThis //调用的this
lastArgs = lastThis = undefined // 清除lastArgs和lastThis
lastInvokeTime = time //上次调用时间为当前时间
result = func.apply(thisArg, args) //调用func,并将结果返回
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) { //超时之前调用 // Reset any `maxWait` timer.
lastInvokeTime = time // Start the timer for the trailing edge.
timerId = startTimer(timerExpired, wait) // 开始timer
// Invoke the leading edge.
return leading ? invokeFunc(time) : result
// 如果leading为true,调用func,否则返回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() { // 刷新timer
const time = Date.now()
if (shouldInvoke(time)) { //如果可以调用,调用trailingEdge
return trailingEdge(time)
}
// Restart the timer.
timerId = startTimer(timerExpired, remainingWait(time)) // 不调用则重置timerId
}
function trailingEdge(time) { //超时之后调用
timerId = undefined // Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) { //如果设置trailing为true,并且有lastArgs,调用func
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 // 调用的this
lastCallTime = time
if (isInvoking) {
if (timerId === undefined) { //首次触发,调用leadingEdge
return leadingEdge(lastCallTime)
}
if (maxing) { // 处理多次频繁的调用
// Handle invocations in a tight loop.
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) { //如果没有timer,设置定时器
timerId = startTimer(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}
export default debouncethrottle.js(断流)
import debounce from './debounce.js'
import isObject from './isObject.js'
/** * Creates a throttled function that only invokes `func` at most once per
* every `wait` milliseconds (or once per browser frame). The throttled 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
* throttled function. Subsequent calls to the throttled 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 throttled function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until the next tick, similar to `setTimeout` with a timeout of `0`.
*
* If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
* invocation will be deferred until the next frame is drawn (typically about * 16ms).
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `throttle` and `debounce`.
*
* @since 0.1.0 * @category Function
* @param {Function} func The function to throttle.
* @param {number} [wait=0]
* The number of milliseconds to throttle invocations to; if omitted,
* `requestAnimationFrame` is used (if available).
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=true]
* Specify invoking on the leading edge of the timeout.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new throttled function.
* @example
*
* // Avoid excessively updating the position while scrolling.
* jQuery(window).on('scroll', throttle(updatePosition, 100))
* // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
* const throttled = throttle(renewToken, 300000, { 'trailing': false })
* jQuery(element).on('click', throttled)
* // Cancel the trailing throttled invocation.
* jQuery(window).on('popstate', throttled.cancel)
*/
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
}
)
}
export default throttle从上面看得出 throttle 是通过debounce来实现,所以也就没有注解了
debounce源码分析
debounce.js这个文件的核心和入口是debounced函数,我们先看看它:
function debounced(...args) {
const time = Date.now()
const isInvoking = shouldInvoke(time) // 是否能调用
lastArgs = args // 记录最后一次调用传入的参数
lastThis = this // 记录最后一次调用的this
lastCallTime = time // 记录最后一次时间
// isInvoking 字面可以表示是否台调用
if (isInvoking) {
if (timerId === undefined) { //首次触发,调用leadingEdge
return leadingEdge(lastCallTime)
}
if (maxing) { // 处理多次频繁的调用 其实这个是和throttle有关的参数
// Handle invocations in a tight loop.
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) { //如果没有timer,设置定时器
timerId = startTimer(timerExpired, wait)
}
return result
}这里面很多变量,用闭包存下的一些值 lastArgs 、lastThis 、lastCallTime
trailingEdge函数其实就是执行一下invokeFunc然后清空一下定时器还有一些上下文,这样下次再执行debounce过的函数的时候就能够继续下一轮了
function trailingEdge(time) { //超时之后调用
timerId = undefined
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) { //如果设置trailing为true,并且有lastArgs,调用func
return invokeFunc(time)
}
lastArgs = lastThis = undefined
return result
}接下来timerExpired的内容
function timerExpired() { // 刷新timer
const time = Date.now()
if (shouldInvoke(time)) { //如果可以调用,调用trailingEdge
return trailingEdge(time)}// Restart the timer.
timerId = startTimer(timerExpired, remainingWait(time)) // 不调用则重置timerId
} 以上要是在没有maxWait 参数情况下要简单实现如下:
// debounce简单实现
const debounce = function(wait, func){
let timerId
return function(...args){
var thisArg = this
clearTimeout(last)
timerId = setTimeout(function(){
func.apply(thisArg, args)
}, wait)
}
}throttle源码分析
其实基本用的都是debounce.js里面的内容,其实就是debounced函数中的如下代码作用:
if (maxing) { // 处理多次频繁的调用
// Handle invocations in a tight loop.
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}可以看到remainingWait和shouldInvoke中也都对maxing进行了判断
总结一下其实就是下面这样,忽略了细节和边界问题
throttle简单实现
var throttle = function(wait, func){
var last = 0 return function(){
var time = +new Date()
if (time - last > wait){
func.apply(this, arguments)
last = curr
}
}
}一开始看的时候有点不理解,因为和我想的这两个的简单实现有很多不同,看一遍时有点不知所云,后通过其现有参数的意图后才慢慢拆分理解,lodash考虑了很多细节和现实场景,如 是否要立即执行、最一次要执行、上一次执行和下一次执行重叠等等