[javascript] 누군가 Javascript에서 “디 바운스”기능을 설명 할 수 있습니까?

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에 잘못된 위치 에있었습니다 .



답변

문제의 코드는 링크의 코드와 약간 변경되었습니다. 링크에서 (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);


답변

여기서 주목해야 할 것은 변수에 “닫힌” 함수debounce생성 한다는 것입니다 . 후에도 생산 함수의 모든 통화 중에 접근 할 변수 체류 자체가 돌아왔다, 그리고 서로 다른 통화를 통해 변경할 수 있습니다.timeouttimeoutdebounce

일반적인 아이디어 debounce는 다음과 같습니다.

  1. 시간 초과없이 시작하십시오.
  2. 생성 된 기능이 호출되면 시간 초과를 지우고 재설정하십시오.
  3. 시간 초과가 발생하면 원래 기능을 호출하십시오.

첫 번째 요점은 바로 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 의 구현도 확인하십시오 .


답변

디 바운스 기능의 작동 방식을 정확하게 설명 하고 데모를 포함하는 JavaScriptDemistifying 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 이벤트가있을 때마다 내부 함수를 호출한다는 것입니다. 모든 크기 조정 이벤트가 배열에 있다고 상상하면 더 분명 할 것입니다.

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다음 반복에 사용할 수 있다는 것을 알 수 있습니까? 그리고 이름 변경에 대한 내 의견으로는, 이유가 없습니다 thiscontentargumentsargs.


답변

이것은보다 설명 적으로 명명 된 변수를 사용하여 처음 호출 될 때 항상 디 바운스 된 함수를 시작하는 변형입니다.

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


답변

자바 스크립트의 간단한 디 바운스 방법

<!-- 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/