178

ここに書かれた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間違った場所にありました。

4

10 に答える 10

154

問題のコードは、リンクのコードからわずかに変更されました。リンクでは(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);

于 2014-06-02T23:36:36.597 に答える
61

ここで注意すべき重要なことは、変数を「閉じた」関数debounceを生成することです。変数は、それ自体が戻った後でも、生成された関数のすべての呼び出し中にアクセス可能なままであり、別の呼び出しで変更できます。timeouttimeoutdebounce

の一般的な考え方debounceは次のとおりです。

  1. タイムアウトなしで開始します。
  2. 生成された関数が呼び出された場合は、タイムアウトをクリアしてリセットします。
  3. タイムアウトになった場合は、元の関数を呼び出します。

最初のポイントはただですvar timeout;、それは確かにただundefinedです。幸いなことに、clearTimeoutはその入力に関してはかなり緩いです:undefinedタイマー識別子を渡しても何もせず、エラーなどをスローしません。

2 番目のポイントは、生成された関数によって実行されます。最初に呼び出しに関する情報 (thisコンテキストとarguments) を変数に格納し、後でこれらをデバウンスされた呼び出しに使用できるようにします。次に、タイムアウトをクリアし (セットが 1 つある場合)、新しいものを作成して を使用して置き換えsetTimeoutます。これは の値を上書きし、timeoutこの値は複数の関数呼び出しにわたって保持されることに注意してください! これにより、デバウンスが実際に機能します。関数が複数回呼び出された場合timeout、新しいタイマーで複数回上書きされます。そうでない場合、複数の呼び出しによって複数のタイマーが開始され、それらはすべてアクティブなままになります。呼び出しは単に遅延されますが、デバウンスはされません。

3 番目のポイントは、タイムアウト コールバックで行われます。変数の設定を解除timeoutし、保存された呼び出し情報を使用して実際の関数呼び出しを行います。

フラグは、タイマーの前またはimmediate関数を呼び出す必要があるかどうかを制御することになっています。の場合タイマーがヒットするまで元の関数は呼び出されません。の場合、元の関数が最初に呼び出され、タイマーがヒットするまでそれ以上呼び出されません。falsetrue

しかし、私はif (immediate && !timeout)チェックが間違っていると信じています:timeoutによって返されたタイマー識別子に設定されたばかりsetTimeoutなので!timeout、常にfalseその時点にあるため、関数を呼び出すことはできません。underscore.js の現在のバージョンでは、を呼び出すimmediate && !timeout に評価するという、わずかに異なるチェックがあるようsetTimeoutです。(アルゴリズムも少し異なります。たとえば、 を使用しませんclearTimeout。) そのため、常に最新バージョンのライブラリを使用するようにしてください。:-)

于 2014-06-02T23:33:44.513 に答える
39

デバウンスされた関数は、呼び出されたときに実行されません。実行前に、構成可能な期間にわたって呼び出しが一時停止するのを待ちます。新しい呼び出しごとにタイマーが再起動します。

調整された関数は実行され、構成可能な期間待機してから、再び起動できるようになります。

Debounce は、キープレス イベントに最適です。ユーザーが入力を開始してから一時停止すると、すべてのキーの押下が 1 つのイベントとして送信されるため、呼び出しの処理が削減されます。

スロットルは、設定された期間に 1 回だけユーザーに呼び出しを許可したいリアルタイム エンドポイントに最適です。

Underscore.jsの実装も確認してください。

于 2014-06-02T23:28:35.447 に答える
1

やりたいことは次のとおりです。次の関数をすぐに呼び出そうとすると、最初の関数はキャンセルされ、新しい関数は指定されたタイムアウトを待ってから実行されます。実際には、最初の関数のタイムアウトをキャンセルする何らかの方法が必要ですか? しかし、どのように?関数を呼び出して、返されたタイムアウト ID を渡し、その ID を新しい関数に渡すことができます。しかし、上記のソリューションはよりエレガントです。

それが行うことはtimeout、返された関数のスコープで変数を効果的に使用できるようにすることです。そのため、「サイズ変更」イベントが発生しても再度呼び出されることはないdebounce()ため、timeoutコンテンツは変更されず (!)、「次の関数呼び出し」で引き続き使用できます。

ここで重要なことは、基本的に、サイズ変更イベントが発生するたびに内部関数を呼び出すことです。おそらく、すべてのサイズ変更イベントが配列内にあると想像すると、より明確になります。

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

于 2014-06-02T23:41:17.113 に答える
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);
    }
  }
};
于 2019-12-05T07:15:56.393 に答える