防抖是一种用于控制我们允许函数随时间执行次数的技术。当一个 JavaScript 函数被防抖,且等待时间为 X 毫秒时,它必须等待,直到自上次调用防抖函数以来经过 X 毫秒。
您几乎肯定在日常生活中遇到过防抖(例如,当进入电梯时)。只有在 X 时间内没有按下“开门”按钮(未调用防抖函数)后,电梯门才会真正关闭(回调函数被执行)。
实现一个 debounce
函数,该函数接受一个回调函数和一个 wait
持续时间。调用 debounce()
返回一个函数,该函数具有防抖上述行为的回调函数的调用。
let i = 0;function increment() {i++;}const debouncedIncrement = debounce(increment, 100);// t = 0: 调用 debouncedIncrement()。debouncedIncrement(); // i = 0// t = 50: i 仍然是 0,因为 100ms 还没有过去。// t = 100: increment() 被调用,现在 i 是 1。
debouncedIncrement()
被多次调用。
let i = 0;function increment() {i++;}const debouncedIncrement = debounce(increment, 100);// t = 0: 调用 debouncedIncrement()。debouncedIncrement(); // i = 0// t = 50: i 仍然是 0,因为 100ms 还没有过去。// 再次调用 debouncedIncrement()。debouncedIncrement(); // i = 0// t = 100: i 仍然是 0,因为它只有// 自 t = 50 以来经过了 50ms 的时间。// t = 150: 因为自// t = 50 以来已经过去了 100ms,// increment 被调用,现在 i 是 1 。
cancel()
方法取消延迟调用,并使用 flush()
方法立即调用它们。防抖是一种用于控制我们允许函数随时间执行次数的技术。当一个 JavaScript 函数被防抖,且等待时间为 X 毫秒时,它必须等待,直到自上次调用防抖函数以来经过 X 毫秒。
您几乎肯定在日常生活中遇到过防抖(例如,当进入电梯时)。只有在 X 时间内没有按下“开门”按钮(未调用防抖函数)后,电梯门才会真正关闭(回调函数被执行)。
实现一个 debounce
函数,该函数接受一个回调函数和一个 wait
持续时间。调用 debounce()
返回一个函数,该函数具有防抖上述行为的回调函数的调用。
let i = 0;function increment() {i++;}const debouncedIncrement = debounce(increment, 100);// t = 0: 调用 debouncedIncrement()。debouncedIncrement(); // i = 0// t = 50: i 仍然是 0,因为 100ms 还没有过去。// t = 100: increment() 被调用,现在 i 是 1。
debouncedIncrement()
被多次调用。
let i = 0;function increment() {i++;}const debouncedIncrement = debounce(increment, 100);// t = 0: 调用 debouncedIncrement()。debouncedIncrement(); // i = 0// t = 50: i 仍然是 0,因为 100ms 还没有过去。// 再次调用 debouncedIncrement()。debouncedIncrement(); // i = 0// t = 100: i 仍然是 0,因为它只有// 自 t = 50 以来经过了 50ms 的时间。// t = 150: 因为自// t = 50 以来已经过去了 100ms,// increment 被调用,现在 i 是 1 。
cancel()
方法取消延迟调用,并使用 flush()
方法立即调用它们。Debounce, along with throttle, are among the most common front end interview questions; it's the front end equivalent of inverting a binary tree. Hence you should make sure that you are very familiar with the question.
Given that there's a wait
duration before the function can be invoked, we know that we will need a timer, and setTimeout
is the first thing that comes to mind.
We will also need to return a function which wraps around the callback function parameter. This function needs to do a few things:
wait
. This is performed using setTimeout
. Since we might need to clear the timer if the debounced function is called again while there's a pending invocation, we need to retain a reference to a timeoutID
, which is the returned value of setTimeout
.wait
duration. We can cancel the timer via clearTimeout(timeoutID)
.Debounced functions are used like the original functions, so we should forward the value of this
and function arguments when invoking the original callback functions.
You may be tempted to use func(...args)
but this
will be lost if callback functions are invoked that way. Hence we have use Function.prototype.apply()
/Function.prototype.call()
which allows us to specify this
as the first argument.
func.apply(thisArg, args)
func.call(thisArg, ...args)
/*** @param {Function} func* @param {number} wait* @return {Function}*/export default function debounce(func, wait = 0) {let timeoutID = null;return function (...args) {// Keep a reference to `this` so that// func.apply() can access it.const context = this;clearTimeout(timeoutID);timeoutID = setTimeout(function () {timeoutID = null; // Not strictly necessary but good to do this.func.apply(context, args);}, wait);};}
The main pitfall in this question is invoking the callback function with the correct this
, the value of this
when the debounced function was called. Since the callback function will be invoked in a timeout, we need to ensure that the first argument to func.apply()
/func.call()
is the right value. There are two ways to achieve this:
this
and access this
via that variable from within the setTimeout
callback. This is the traditional way of preserving this
before arrow functions existed.setTimeout
callback where the this
value within it has lexical scope. The value of this
within arrow functions is bound to the context in which the function is created, not to the environment in which the function is called./*** @callback func* @param {number} wait* @return {Function}*/export default function debounce(func, wait = 0) {let timeoutID = null;return function (...args) {clearTimeout(timeoutID);timeoutID = setTimeout(() => {timeoutID = null; // Not strictly necessary but good to include.// Has the same `this` as the outer function's// as it's within an arrow function.func.apply(this, args);}, wait);};}
Also, we should not implement the returned function using an arrow function for reasons mentioned above. The this
value of the returned function needs to be dynamically determined when executed.
Read this article for a more in-depth explanation.
setTimeout
this
的工作原理Function.prototype.apply()
/Function.prototype.call()
调用函数clearTimeout()
是一个宽容的函数,将无效的 ID 传递给 clearTimeout()
会默默地什么也不做;不会抛出异常。因此,在使用 clearTimeout()
之前,我们不必检查 timeoutID === null
。
console.log()
语句将显示在此处。