JavaScript中的“debounce”函数是什么?

199

我对JavaScript中的"debouncing"函数很感兴趣,参考链接: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 的位置不正确。


1
如果您使用的不是有效的计时器ID调用clearTimeout,它将不会执行任何操作。 - Ry-
@false,这是有效的标准行为吗? - Pacerier
3
是的,它在规范中有说明(http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-cleartimeout):“如果 handle 不能识别出在调用该方法的WindowTimers对象的活动定时器列表中的一个条目,则该方法不执行任何操作。” - Mattias Buelens
13个回答

169
问题中的代码稍微修改了原链接中的代码。在链接中,会在创建新的超时之前检查(immediate && !timeout),这个检查会导致立即模式永远不会触发。我已经更新了我的答案,注释了来自链接的工作版本。

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


1
对于immediate && timeout检查,是否总会有一个timeout(因为timeout先被调用)。此外,当它被声明(使其未定义)并在之前被清除时,clearTimeout(timeout)有什么好处呢? - Startec
1
为什么setTimeout函数内的超时时间必须设置为null?此外,我尝试了这段代码,对我来说,将immediate参数设置为true只会完全阻止函数被调用(而不是在延迟后被调用)。你有这种情况吗? - Startec
我支持上面的问题:为什么/应该将 immediate 设置为 true?直接调用 func 而不是防抖岂不更容易? - Gui Imamura
2
如果您只是调用函数,那么在该函数再次被调用之前,您无法强制等待计时器。想象一下用户狂按开火键的游戏。您希望火焰立即触发,但无论用户多快地按下按钮,都不会在另外X毫秒内再次触发。 - Malk
我不理解这个。如果我运行 debounce(somfunction, 0.5, false);,什么都不会发生。有什么诀窍吗? - geoidesic
显示剩余5条评论

64
重要的是要注意,debounce生成了一个“封闭”在timeout变量上的函数。即使在debounce自身已经返回之后,timeout变量在每次调用生成的函数时仍然可以访问且可以在不同的调用中改变。 debounce的一般思路如下:
  1. 开始没有超时时间。
  2. 如果调用生成的函数,则清除并重置超时时间。
  3. 如果超时,请调用原始函数。
第一点只是var timeout;,实际上只是undefined。幸运的是,clearTimeout对其输入相当宽松:传递一个未定义的计时器标识符将导致它什么也不做,它不会抛出错误或其他什么异常。
第二点是由生成的函数完成的。它首先使用变量存储有关调用的信息(this上下文和arguments),以便稍后可以将其用于防抖调用。然后清除超时(如果已设置),然后创建一个新的超时来替换它,使用setTimeout。请注意,这将覆盖timeout的值,并且此值在多个函数调用期间保持不变!这允许防抖实际起作用:如果多次调用函数,则timeout将被多次覆盖为一个新的计时器。如果不是这种情况,则会启动多个计时器,它们全部保持活动状态 - 调用只会被延迟,但不会被防抖。
第三点在超时回调中完成。它取消设置timeout变量并使用存储的调用信息执行实际的函数调用。immediate标志用于控制函数是在定时器还是调用。如果为false,则直到定时器触发后才会调用原始函数。如果为true,则首先调用原始函数,并且在定时器触发之前不会再次调用。
然而,我认为if(immediate && !timeout)检查是错误的:此时timeout刚刚被设置为setTimeout返回的计时器标识符,因此!timeout总是false,因此该函数永远无法被调用。underscore.js的当前版本似乎有一个稍微不同的检查,它在调用setTimeout之前评估immediate && !timeout。(算法也有点不同,例如它不使用clearTimeout。)这就是为什么你应该尽量使用库的最新版本。 :-)

1
请注意,这将覆盖timeout的值,并且该值在多个函数调用中保持不变。timeout不是每次抖动调用都是本地的吗?它是使用var声明的。为什么会被每次重写?此外,为什么要在结尾检查!timeout?为什么它不总是存在(因为它设置为setTimeout(function() etc.))? 注:timeout指的是JavaScript代码中的一个计时器对象。 - Startec
2
@Startec 它是每次调用“debounce”时本地的,但它在对返回的函数(即您要使用的函数)的多个调用之间共享。例如,在g = debounce(f, 100)中,timeout的值会持续多次调用g。我认为最后的!timeout检查是一个错误,并且它不在当前的underscore.js代码中。 - Mattias Buelens
为什么超时需要在返回函数中尽早清除(就在它声明后)?此外,在setTimeout函数内部将其设置为null。这不是多余的吗?(首先清除,然后将其设置为“null”。在我对上述代码进行的测试中,将immediate设置为true会使函数根本不调用,正如您所提到的。有没有不使用underscore的解决方案? - Startec

44

被防抖函数被调用时不会立即执行,它们会等待一段可配置的时间间隔内的调用暂停后才执行;每次新的调用都会重新启动计时器。

被节流函数在执行后会等待一个可配置的时间间隔才能再次触发执行。

防抖非常适合处理按键事件;当用户开始输入并暂停时,将所有按键作为单个事件提交,从而减少处理调用。

节流非常适用于实时的端点,您只想允许用户在一定时间内调用一次。

查看Underscore.js他们的实现也很好。


32

我第一次遇到去抖函数时,也没有完全理解它的工作原理。虽然它们在大小上相对较小,但实际上它们使用了一些非常先进的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);
        }
    };
};

我在YouTube视频中看到了这个实现,一开始有点难以理解。这个详细的解释帮助我理解了。非常感谢。 - undefined

14

现在我们都在使用Promise

许多我见过的实现方法都过于复杂或存在其他卫生问题。现在是2021年,我们已经长期使用Promise了 - 也有很好的原因。Promise可以清理异步程序,并减少出错的机会。在本文中,我们将编写自己的debounce。这个实现将会:

  • 在任何时候(每个被防抖的任务)只有一个待处理的promise
  • 通过正确取消待处理的promise来停止内存泄漏
  • 仅解决最新的promise
  • 通过实时代码演示演示正确的行为

我们使用task和延迟毫秒数ms作为debounce的两个参数进行编写。我们引入一个单一的局部绑定t作为它的局部状态 -

function debounce (task, ms) {
  let t = { promise: null, cancel: _ => void 0 }
  return async (...args) => {
    try {
      t.cancel()
      t = deferred(ms)
      await t.promise
      await task(...args)
    }
    catch (_) { /* prevent memory leak */ }
  }
}

我们依赖于一个可重复使用的deferred函数,该函数创建一个在ms毫秒后解决的新的promise。它引入了两个本地绑定,即promise本身和取消它的能力。
function deferred (ms) {
  let cancel, promise = new Promise((resolve, reject) => {
    cancel = reject
    setTimeout(resolve, ms)
  })
  return { promise, cancel }
}

点击计数器示例

在这个第一个示例中,我们有一个按钮来统计用户的点击次数。事件监听器使用debounce进行附加,因此只有在指定的持续时间后才会增加计数器 -

// debounce, deferred
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }

// dom references
const myform = document.forms.myform
const mycounter = myform.mycounter

// event handler
function clickCounter (event) {
  mycounter.value = Number(mycounter.value) + 1
}

// debounced listener
myform.myclicker.addEventListener("click", debounce(clickCounter, 1000))
<form id="myform">
<input name="myclicker" type="button" value="click" />
<output name="mycounter">0</output>
</form>

现场查询示例,“自动完成”

在这个第二个例子中,我们有一个带有文本输入的表单。我们使用debounce附加了我们的search查询 -

// debounce, deferred
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }

// dom references
const myform = document.forms.myform
const myresult = myform.myresult

// event handler
function search (event) {
  myresult.value = `Searching for: ${event.target.value}`
}

// debounced listener
myform.myquery.addEventListener("keypress", debounce(search, 1000))
<form id="myform">
<input name="myquery" placeholder="Enter a query..." />
<output name="myresult"></output>
</form>

多重防抖,React钩子useDebounce

另一个问答中,有人询问是否可以使用暴露出的防抖取消机制来创建一个useDebounce React钩子。使用上面的deferred,这是一项微不足道的练习。

// revised implementation
function debounce(task, ms) {
  let t = { promise: null, cancel: _ => void 0 }
  return [
   // ...,
    _ => t.cancel() // ✅ return cancellation mechanism
  ]
}

// revised usage
const [inc, cancel] = debounce(clickCounter, 1000) // ✅ two controls
myform.mybutton.addEventListener("click", inc)
myform.mycancel.addEventListener("click", cancel)

实现一个useDebounce的React hook非常简单 -

function useDebounce(task, ms) {
  const [f, cancel] = debounce(task, ms)
  useEffect(_ => cancel) // ✅ auto-cancel when component unmounts
  return [f, cancel]
}

请前往原始问答查看完整演示。


1
@EltonLin 我在这个答案中添加了一些注释,并链接了另一个问答,展示了防抖在更复杂的示例中的使用。 - Mulan
这可能看起来很花哨,但对我没用。我传递的函数仍然运行了多次,当我增加超时时间时情况变得更糟。 - Post Impatica
@Mulan 是的,有些我自己代码里不理解的地方。对此感到抱歉。 - Post Impatica
1
let cancel, promise = new Promise(...)let cancel = undefined, promise = new Promise(...) 是相同的。cancel 绑定必须在 Promise 构造函数之外声明。或者我可以写成 let cancel; let promise = new Promise(...)。它们都是做同样的事情。 - Mulan
1
@Adnan,当然可以这样做,这几乎没有任何影响。一旦承诺被拒绝,任何解决它的尝试都会被有效地忽略。请根据您的需求调整实现 ^_^ - Mulan
显示剩余7条评论

3
一份简单的防抖函数:
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));

1
一个简单的 JavaScript 防抖方法:

基本 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>

JavaScript文件

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

驱动代码

debouncebtn.addEventListener('click', debounce(function() {
    document.getElementById('message').innerHTML += '<br/> The button only triggers every 3 seconds how much every you fire an event';
  console.log('The button only triggers every 3 seconds how much every you fire an event');
}, 3000))

运行时 JSFiddle 示例:https://jsfiddle.net/arbaazshaikh919/d7543wqe/10/


很遗憾,我无法从 args = arguments 这部分中理解任何意义。这些参数是从哪里来的?我认为你需要将其设置为返回函数的参数。 - Sandy B
这句话难以理解: "......每次触发事件时有多少". 你能修复它吗?(但请勿添加"编辑:","更新:"或类似的字样 - 回答应该看起来像是今天写的) - Peter Mortensen

1
这是一个变体,它总是在第一次调用时触发防抖函数,并使用更具描述性的变量名称:
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);
    }
  }
};

1
假设有一个用户在元素上以每秒5个字符的速度输入句子,并且您正在监听来查询API并获取用户输入的部分文本。为了节省资源,您不想在每次按键时调用API,而是在一定间隔或检测到无按键一段时间后再进行调用。这就是去抖动,并在下面的GIF中表示:每当触发回调时,输入框会变成红色。

Debouncing examples

该GIF展示了四种不同的方法:

  • 无去抖动。 func
  • 延迟调用并取消之前的延迟调用。 debounce(func, 300)
  • 如果可能,立即调用并延迟未来的调用。否则延迟调用。 debounce(func, 300, { immediate: true })
  • 延迟加上一些强制间隔以连续反馈,以防用户连续输入长句子而没有暂停。 debounce(func, 300, { interval: 1500 })

对于这个例子,immediate=false 更合理,但对于一个按钮,immediate=true 更合理。

function debounce(func, delay, { immediate = false, interval = 1e30 } = {}) {
  let awaiting = false;
  let last_params, deadline_A, deadline_B = 1e30;
  async function deadlineSleep() {
    while (true) {
      if (deadline_B + delay < Date.now()) deadline_B = 1e30;
      let ms = Math.min(deadline_A, deadline_B) - Date.now();
      if (ms <= 0) return;
      await new Promise(resolve => setTimeout(resolve, ms));
    }
  }
  async function wrapper(...args) {
    last_params = { arg0: this, args };
    deadline_A = Date.now() + delay;
    if (awaiting) return;
    awaiting = true;
    if (!immediate) await deadlineSleep();
    while (last_params) {
      const { arg0, args } = last_params;
      last_params = null;
      deadline_B = Date.now() + interval;
      try { await func.apply(arg0, args); }
      catch (e) { console.error(e); }
      await deadlineSleep();
    }
    awaiting = false;
  };
  return wrapper;
}

形式上,wrapper = debounce(func, delay, {immediate, interval}) 遵循以下规则:

  • 尽早地使用最后接收到的参数调用func,同时遵守其他规则。
  • 如果func是异步的,则不会有两个func并行运行。
  • wrapper的最后一次调用始终触发func的调用。
  • delay毫秒的时间窗口内不要多次调用func
  • 如果上一次(或上上次如果immediate=true)调用wrapper的时间距离现在少于delay毫秒,则延迟func的执行。否则,立即执行func
  • 如果有一个延迟了interval毫秒的调用链,请覆盖前面的规则并立即执行func

1
你想要做以下事情:如果您尝试在另一个函数之后立即调用一个函数,则应该取消第一个函数,并等待给定的超时,然后执行新函数。因此,实际上您需要一种方法来取消第一个函数的超时?但是如何操作?
你可以调用该函数,并传递返回的超时ID,然后将该ID传递到任何新函数中。但上述解决方案更加优雅。
它有效地使得 `timeout` 变量在返回函数的范围内可用。因此,当“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 可用于下一次迭代吗?我认为没有理由将 this 重命名为 content,将 arguments 重命名为 args

1
“重命名”是绝对必要的。在setTimeout()回调函数内,“this”和“arguments”的含义会发生变化。您必须将其复制到其他地方,否则信息将丢失。 - CubicleSoft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接