我对javascript中的“反跳”功能感兴趣,写在这里:http : //davidwalsh.name/javascript-debounce-function
不幸的是,代码的解释不够清楚,我无法理解。谁能帮我弄清楚它是如何工作的(我在下面留下了我的评论)。简而言之,我真的不明白这是如何工作的
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
编辑:复制的代码片段以前callNow
在错误的位置。
问题中的代码与链接中的代码略有不同。在链接中,(immediate && !timeout)
在创建新的timout之前要进行检查。之后拥有它会导致即时模式从不触发。我已经更新了答案,以便从链接中注释工作版本。
function debounce(func, wait, immediate) {
// 'private' variable for instance
// The returned function will be able to reference this due to closure.
// Each call to the returned function will share this common timer.
var timeout;
// Calling debounce returns a new anonymous function
return function() {
// reference the context and args for the setTimeout function
var context = this,
args = arguments;
// Should the function be called now? If immediate is true
// and not already in a timeout then the answer is: Yes
var callNow = immediate && !timeout;
// This is the basic debounce behaviour where you can call this
// function several times, but it will only execute once
// [before or after imposing a delay].
// Each time the returned function is called, the timer starts over.
clearTimeout(timeout);
// Set the new timeout
timeout = setTimeout(function() {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timeout = null;
// Check if the function already ran with the immediate flag
if (!immediate) {
// Call the original function with apply
// apply lets you define the 'this' object as well as the arguments
// (both captured before setTimeout)
func.apply(context, args);
}
}, wait);
// Immediate mode and no wait timer? Execute the function..
if (callNow) func.apply(context, args);
}
}
/////////////////////////////////
// DEMO:
function onMouseMove(e){
console.clear();
console.log(e.x, e.y);
}
// Define the debounced function
var debouncedMouseMove = debounce(onMouseMove, 50);
// Call the debounced function on every mouse move
window.addEventListener('mousemove', debouncedMouseMove);
这里要注意的重要事情是debounce
产生一个“封闭”变量的函数timeout
。timeout
即使在debounce
返回产生的函数之后,该变量在每次调用产生的函数期间仍保持可访问状态,并且可以在不同的调用之间进行切换。
的总体思路debounce
如下:
- 从没有超时开始。
- 如果调用了产生的函数,请清除并重置超时。
- 如果超时,请调用原始函数。
第一点是正义var timeout;
,的确是正义undefined
。幸运的是,clearTimeout
它的输入相当宽松:传递undefined
计时器标识符会使它什么都不做,它不会引发错误或其他任何事情。
第二点由产生的函数完成。它首先在变量中存储有关该调用的一些信息(this
上下文和arguments
),以便以后可以将它们用于去抖动的调用。然后清除超时(如果有一组),然后创建一个新的以替换它setTimeout
。请注意,这会覆盖的值,timeout
并且该值会在多个函数调用中持续存在!这样可以使防抖动实际起作用:如果多次调用该函数,timeout
则使用新的计时器多次覆盖该函数。如果不是这种情况,则多次调用将导致启动多个计时器,这些计时器都保持活动状态-调用只会延迟,而不会被去抖。
第三点是在超时回调中完成的。它取消设置timeout
变量,并使用存储的调用信息进行实际的函数调用。
该immediate
标志应该控制是在计时器之前还是之后调用该函数。如果为false
,则在点击计时器后才调用原始函数。如果为true
,则将首先调用原始函数,直到点击计时器,该函数才会被调用。
但是,我确实认为if (immediate && !timeout)
检查是错误的:timeout
刚刚将其设置为返回的计时器标识符,setTimeout
因此!timeout
始终false
在该点处进行,因此永远不能调用该函数。当前版本的underscore.js似乎有一些不同的检查,它immediate && !timeout
在调用之前对其进行评估setTimeout
。(算法也有所不同,例如,它不使用clearTimeout
。)因此,您应该始终尝试使用最新版本的库。:-)
防反跳函数在调用时不会执行,它们会在可配置的持续时间内等待调用暂停。每次新的调用都会重新启动计时器。
受限制的函数将执行,然后等待一段可配置的持续时间,然后才有资格再次触发。
防抖功能可用于按键事件。当用户开始键入然后暂停时,您将所有按键提交作为单个事件提交,从而减少了处理调用。
节流非常适合实时端点,您只希望允许用户在设定的时间段内调用一次即可。
还要查看Underscore.js的实现。
我在JavaScript中撰写了一篇题为Demistifying Debounce的文章,在其中我确切地解释了去抖动功能的工作原理并包括一个演示。
当我第一次遇到反跳功能时,我也不太了解。尽管尺寸相对较小,但是它们实际上采用了一些相当高级的JavaScript概念!牢牢把握范围,闭包和setTimeout
方法将有所帮助。
话虽如此,以下是我上面引用的我的帖子中解释和演示的基本去抖动功能。
成品
// Create JD Object
// ----------------
var JD = {};
// Debounce Method
// ---------------
JD.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if ( !immediate ) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait || 200);
if ( callNow ) {
func.apply(context, args);
}
};
};
说明
// Create JD Object
// ----------------
/*
It's a good idea to attach helper methods like `debounce` to your own
custom object. That way, you don't pollute the global space by
attaching methods to the `window` object and potentially run in to
conflicts.
*/
var JD = {};
// Debounce Method
// ---------------
/*
Return a function, that, as long as it continues to be invoked, will
not be triggered. The function will be called after it stops being
called for `wait` milliseconds. If `immediate` is passed, trigger the
function on the leading edge, instead of the trailing.
*/
JD.debounce = function(func, wait, immediate) {
/*
Declare a variable named `timeout` variable that we will later use
to store the *timeout ID returned by the `setTimeout` function.
*When setTimeout is called, it retuns a numeric ID. This unique ID
can be used in conjunction with JavaScript's `clearTimeout` method
to prevent the code passed in the first argument of the `setTimout`
function from being called. Note, this prevention will only occur
if `clearTimeout` is called before the specified number of
milliseconds passed in the second argument of setTimeout have been
met.
*/
var timeout;
/*
Return an anomymous function that has access to the `func`
argument of our `debounce` method through the process of closure.
*/
return function() {
/*
1) Assign `this` to a variable named `context` so that the
`func` argument passed to our `debounce` method can be
called in the proper context.
2) Assign all *arugments passed in the `func` argument of our
`debounce` method to a variable named `args`.
*JavaScript natively makes all arguments passed to a function
accessible inside of the function in an array-like variable
named `arguments`. Assinging `arguments` to `args` combines
all arguments passed in the `func` argument of our `debounce`
method in a single variable.
*/
var context = this, /* 1 */
args = arguments; /* 2 */
/*
Assign an anonymous function to a variable named `later`.
This function will be passed in the first argument of the
`setTimeout` function below.
*/
var later = function() {
/*
When the `later` function is called, remove the numeric ID
that was assigned to it by the `setTimeout` function.
Note, by the time the `later` function is called, the
`setTimeout` function will have returned a numeric ID to
the `timeout` variable. That numeric ID is removed by
assiging `null` to `timeout`.
*/
timeout = null;
/*
If the boolean value passed in the `immediate` argument
of our `debouce` method is falsy, then invoke the
function passed in the `func` argument of our `debouce`
method using JavaScript's *`apply` method.
*The `apply` method allows you to call a function in an
explicit context. The first argument defines what `this`
should be. The second argument is passed as an array
containing all the arguments that should be passed to
`func` when it is called. Previously, we assigned `this`
to the `context` variable, and we assigned all arguments
passed in `func` to the `args` variable.
*/
if ( !immediate ) {
func.apply(context, args);
}
};
/*
If the value passed in the `immediate` argument of our
`debounce` method is truthy and the value assigned to `timeout`
is falsy, then assign `true` to the `callNow` variable.
Otherwise, assign `false` to the `callNow` variable.
*/
var callNow = immediate && !timeout;
/*
As long as the event that our `debounce` method is bound to is
still firing within the `wait` period, remove the numerical ID
(returned to the `timeout` vaiable by `setTimeout`) from
JavaScript's execution queue. This prevents the function passed
in the `setTimeout` function from being invoked.
Remember, the `debounce` method is intended for use on events
that rapidly fire, ie: a window resize or scroll. The *first*
time the event fires, the `timeout` variable has been declared,
but no value has been assigned to it - it is `undefined`.
Therefore, nothing is removed from JavaScript's execution queue
because nothing has been placed in the queue - there is nothing
to clear.
Below, the `timeout` variable is assigned the numerical ID
returned by the `setTimeout` function. So long as *subsequent*
events are fired before the `wait` is met, `timeout` will be
cleared, resulting in the function passed in the `setTimeout`
function being removed from the execution queue. As soon as the
`wait` is met, the function passed in the `setTimeout` function
will execute.
*/
clearTimeout(timeout);
/*
Assign a `setTimout` function to the `timeout` variable we
previously declared. Pass the function assigned to the `later`
variable to the `setTimeout` function, along with the numerical
value assigned to the `wait` argument in our `debounce` method.
If no value is passed to the `wait` argument in our `debounce`
method, pass a value of 200 milliseconds to the `setTimeout`
function.
*/
timeout = setTimeout(later, wait || 200);
/*
Typically, you want the function passed in the `func` argument
of our `debounce` method to execute once *after* the `wait`
period has been met for the event that our `debounce` method is
bound to (the trailing side). However, if you want the function
to execute once *before* the event has finished (on the leading
side), you can pass `true` in the `immediate` argument of our
`debounce` method.
If `true` is passed in the `immediate` argument of our
`debounce` method, the value assigned to the `callNow` variable
declared above will be `true` only after the *first* time the
event that our `debounce` method is bound to has fired.
After the first time the event is fired, the `timeout` variable
will contain a falsey value. Therfore, the result of the
expression that gets assigned to the `callNow` variable is
`true` and the function passed in the `func` argument of our
`debounce` method is exected in the line of code below.
Every subsequent time the event that our `debounce` method is
bound to fires within the `wait` period, the `timeout` variable
holds the numerical ID returned from the `setTimout` function
assigned to it when the previous event was fired, and the
`debounce` method was executed.
This means that for all subsequent events within the `wait`
period, the `timeout` variable holds a truthy value, and the
result of the expression that gets assigned to the `callNow`
variable is `false`. Therefore, the function passed in the
`func` argument of our `debounce` method will not be executed.
Lastly, when the `wait` period is met and the `later` function
that is passed in the `setTimeout` function executes, the
result is that it just assigns `null` to the `timeout`
variable. The `func` argument passed in our `debounce` method
will not be executed because the `if` condition inside the
`later` function fails.
*/
if ( callNow ) {
func.apply(context, args);
}
};
};
您要执行的操作如下:如果尝试立即调用另一个函数,则应取消第一个函数,而新的函数应等待给定的超时,然后执行。因此,实际上,您需要某种方式来取消第一个函数的超时?但是如何?您可以调用该函数,并传递返回的超时ID,然后将该ID传递给任何新函数。但是上面的解决方案更优雅。
它的作用是有效地使timeout
变量在返回函数的范围内可用。因此,当触发“调整大小”事件时,它不会debounce()
再次调用,因此timeout
内容不会更改(!),并且仍可用于“下一个函数调用”。
基本上,这里的关键是每次遇到resize事件时,我们都会调用内部函数。如果我们想象所有的resize-events都在一个数组中,则可能更清楚:
var events = ['resize', 'resize', 'resize'];
var timeout = null;
for (var i = 0; i < events.length; i++){
if (immediate && !timeout) func.apply(this, arguments);
clearTimeout(timeout); // does not do anything if timeout is null.
timeout = setTimeout(function(){
timeout = null;
if (!immediate) func.apply(this, arguments);
}
}
您看到timeout
可供下次迭代使用吗?而且也没有理由,在我看来,重命名this
,以content
和arguments
到args
。
这是一个变体,总是在第一次调用去抖动功能时触发它,并使用更具描述性的变量:
function debounce(fn, wait = 1000) {
let debounced = false;
let resetDebouncedTimeout = null;
return function(...args) {
if (!debounced) {
debounced = true;
fn(...args);
resetDebouncedTimeout = setTimeout(() => {
debounced = false;
}, wait);
} else {
clearTimeout(resetDebouncedTimeout);
resetDebouncedTimeout = setTimeout(() => {
debounced = false;
fn(...args);
}, wait);
}
}
};
JavaScript中的简单反跳方法
<!-- Basic HTML -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Debounce Method</title>
</head>
<body>
<button type="button" id="debounce">Debounce Method</button><br />
<span id="message"></span>
</body>
</html>
// JS File
var debouncebtn = document.getElementById('debounce');
function debounce(func, delay){
var debounceTimer;
return function () {
var context = this, args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function() {
func.apply(context, args)
}, delay);
}
}
// Driver Code
debouncebtn.addEventListener('click', debounce(function() {
document.getElementById('message').innerHTML += '<br/> Button only triggeres is every 3 secounds how much every you fire an event';
console.log('Button only triggeres in every 3 secounds how much every you fire an event');
},3000))
运行时示例JSFiddle:https ://jsfiddle.net/arbaazshaikh919/d7543wqe/10/
简单的反跳功能:-
HTML:-
<button id='myid'>Click me</button>
Javascript:-
function debounce(fn, delay) {
let timeoutID;
return function(...args){
if(timeoutID) clearTimeout(timeoutID);
timeoutID = setTimeout(()=>{
fn(...args)
}, delay);
}
}
document.getElementById('myid').addEventListener('click', debounce(() => {
console.log('clicked');
},2000));
文章标签:debouncing , javascript
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!