有人可以解释JavaScript中的“去抖”function

我对JavaScript中的“debouncing”函数感兴趣,写在这里: 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在错误的地方。

这里需要注意的是, debounce产生一个“closures” timeoutvariables的函数timeoutvariables在每次调用生成的函数期间都可以访问,即使在debounce本身已经返回之后,也可以在不同的调用之间切换。

debounce的总体思路如下:

  1. 从没有超时开始。
  2. 如果生成的函数被调用,清除并重置超时。
  3. 如果超时被击中,请调用原始function。

第一点只是var timeout; ,这确实是undefined 。 幸运的是, clearTimeout的input相当宽松:传递一个undefined定时器标识符会导致它不做任何事情,也不会抛出错误或其他东西。

第二点是由生成的函数完成的。 它首先在variables中存储一些关于调用的信息( this上下文和arguments ),以便稍后可以将这些信息用于去抖动调用。 然后它清除超时(如果有一组),然后创build一个新的使用setTimeoutreplace它。 请注意,这将覆盖timeout的值,并且此值在多个函数调用中仍然存在! 这允许去抖实际上工作:如果函数被多次调用, timeout会被一个新的定时器覆盖多次。 如果情况并非如此,则多次调用会导致多个定时器被启动, 这些定时器都将保持活动状态 – 这些调用只会被延迟,但不会被消除。

第三点是在超时callback中完成的。 它取消设置timeoutvariables,并使用存储的呼叫信息进行实际的function调用。

immediate标志应该控制在定时器之前还是之后调用该函数。 如果它是false ,直到定时器命中之后才会调用原始函数。 如果是true ,原来的函数首先被调用,直到定时器被触发时才会被调用。

但是,我确实认为if (immediate && !timeout)检查是错误的: timeout被设置为由setTimeout返回的计时器标识符,因此!timeout在此时总是为false ,因此函数永远不会被调用。 underscore.js的当前版本似乎有一个稍微不同的检查,它调用setTimeout 之前评估immediate && !timeout 。 (该algorithm也有点不同,例如它不使用clearTimeout 。)这就是为什么你应该总是尝试使用你的库的最新版本。 🙂

问题中的代码从链接中的代码略有改变。 在链接中,有一个检查(immediate && !timeout)然后创build一个新的时间。 有了它后立即模式永远不会开火。 我已经更新了我的答案,从链接注释工作版本。

 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); }; }; 

被调用的函数在被调用时不执行,它们在执行之前等待一段时间的调用暂停。 每个新的调用重新启动计时器。

被限制的function执行,然后等待一个可configuration的时间,然后才有资格再次触发。

反弹对于按键事件来说是很棒的。 当用户开始键入然后暂停时,您将所有按键作为单个事件提交,从而减less处理调用。

Throttle对于实时端点来说是非常好的,你只想让用户在一段时间内调用一次。

查看Underscore.js的实现。

我写了一篇标题为“ Demistifying Debounce in JavaScript ”的文章,其中我详细解释了debounce 函数是如何工作的,并包含演示。

我也没有完全理解,当我第一次遇到一个去抖function时,它是如何工作的。 虽然规模相对较小,但实际上却采用了一些非常先进的JavaScript概念! 掌握范围,closures和setTimeout方法将有所帮助。

这就是说,下面是我在上面引用的post中解释和演示的基本的去抖function。

成品

 // 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); } }; }; 

你想要做的是如下:如果你试图调用一个接一个的函数,第一个应该被取消 ,新的应该等待给定的超时,然后执行。 所以实际上你需要一些方法来取消第一个函数的超时? 但是,如何? 你可以调用这个函数,并且传递返回的timeout-id,然后把这个ID传递给任何新的函数。 但是上面的解决scheme更加优雅。

它所做的是有效地使timeoutvariables在返回函数的作用域中可用。 因此,当触发“resize”事件时,它不会再次调用debounce() ,因此timeout内容不会被更改(!),并且仍然可用于“下一个函数调用”。

这里关键的一点是,我们每次resize事件时都会调用内部函数。 如果我们想象所有resize事件是在一个数组中,也许是更清楚:

 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可用于下一次迭代? 在我看来,没有任何理由将其重命名为contentarguments

Interesting Posts