development

누군가 Javascript에서 "디 바운스"기능을 설명 할 수 있습니까?

big-blog 2020. 7. 4. 09:27
반응형

누군가 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를 전달합니다. 그러나 위의 솔루션은 더 우아합니다.

What it does is effectively make the timeout variable available in the scope of returned function. So when a 'resize' event is fired it does not call debounce() again, hence the timeout content is not changed (!) and still available for the "next function call".

The key thing here is basically that we call the internal function every time we have a resize event. Perhaps it is more clear if we imagine all resize-events is in an array:

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

You see the timeout is available to the next iteration? And there is no reason, in my opinion to rename this to content and arguments to args.

참고URL : https://stackoverflow.com/questions/24004791/can-someone-explain-the-debounce-function-in-javascript

반응형