programing

자바스크립트의 단순 스로틀

stoneblock 2023. 9. 11. 21:25

자바스크립트의 단순 스로틀

자바스크립트에서 간단한 스로틀을 찾고 있습니다.lodash나 언더스코어와 같은 라이브러리들이 그것을 가지고 있다는 것을 알고 있지만, 오직 하나의 기능에 대해서만 그러한 라이브러리들을 포함하는 것은 과도한 일이 될 것입니다.

jQuery와 비슷한 기능이 있는지도 확인하고 있었는데 찾을 수 없었습니다.

작동하는 스로틀을 발견했고, 여기 코드가 있습니다.

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

이것의 문제점은 스로틀 시간이 완료된 후에 한 번 더 기능을 실행한다는 것입니다.따라서 키 누름 시 10초마다 발생하는 스로틀을 만들었다고 가정해 보겠습니다. 키 누름을 2번 수행해도 10초가 완료된 후에도 두 번째 키 누름이 발생합니다.저는 이런 행동을 원하지 않습니다.

underscore.js 또는 lodash 소스 코드를 사용하여 이 함수의 테스트가 잘 된 버전을 찾습니다.

언더스코어.js 자체에 대한 모든 참조를 제거하기 위해 언더스코어 코드를 약간 수정한 버전은 다음과 같습니다.

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = Date.now();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

지원을 강조하는 모든 옵션이 필요하지 않다면 이 코드를 단순화할 수 있습니다.

아래에서 이 기능의 매우 단순하고 구성할 수 없는 버전을 찾으십시오.

function throttle (callback, limit) {
    var waiting = false;                      // Initially, we're not waiting
    return function () {                      // We return a throttled function
        if (!waiting) {                       // If we're not waiting
            callback.apply(this, arguments);  // Execute users function
            waiting = true;                   // Prevent future invocations
            setTimeout(function () {          // After a period of time
                waiting = false;              // And allow future invocations
            }, limit);
        }
    }
}

편집 1: 밑줄에 대한 또 다른 참조, thx에서 @Zettam의 코멘트를 제거했습니다.

편집 2: lodash 및 코드 단순화 가능성에 대한 제안 추가, @lolzery @wowzery의 코멘트에 thx

편집 3: 인기 있는 요청으로 인해 @vsync의 코멘트를 차용하여 매우 단순하고 구성할 수 없는 버전의 함수를 추가했습니다.

이거는?

function throttle(func, timeFrame) {
  var lastTime = 0;
  return function () {
      var now = Date.now();
      if (now - lastTime >= timeFrame) {
          func();
          lastTime = now;
      }
  };
}

간단하죠.

출처를 확인하는 것에 관심이 있을 것입니다.

callback: 호출해야 하는 함수를 사용합니다.

limit: 제한 시간 내에 함수를 호출해야 하는 횟수

time: 제한 카운트를 재설정하기 위한 시간 범위

기능용도:사용자가 1분에 10번 호출할 수 있는 API가 있다고 가정합니다.

function throttling(callback, limit, time) {
    /// monitor the count
    var calledCount = 0;

    /// refresh the `calledCount` varialbe after the `time` has been passed
    setInterval(function(){ calledCount = 0 }, time);

    /// creating a closure that will be called
    return function(){
        /// checking the limit (if limit is exceeded then do not call the passed function
        if (limit > calledCount) {
            /// increase the count
            calledCount++;
            callback(); /// call the function
        } 
        else console.log('not calling because the limit has exceeded');
    };
}
    
//////////////////////////////////////////////////////////// 
// how to use

/// creating a function to pass in the throttling function 
function cb(){
    console.log("called");
}

/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);

방문자들을 ) 논의를 , 의 에( 을 를 가 을 의 가 하지 을 의 )throttlelodash번들을 의지는을는것는이다면지은다이는ysoe'es,더r,oean은teerd의throttle의 묶음 안에lodash 예를 들어 ES6의 경우 다음과 같습니다.

import throttle from 'lodash/throttle';

그리고 또 하나는.throttle지의 온리 lodash간단한 것과 함께 사용할 수 있는 calling.import ES6는requireES서에서

윈도우 크기 조절 이벤트를 위해 스로틀/디바운스 기능이 필요했는데 궁금해서 이것들이 무엇인지, 어떻게 작동하는지도 알고 싶었습니다.

SO에 대한 여러 블로그 게시물과 QA를 읽어 보았지만, 이들은 모두 이를 지나치게 복잡하게 하거나 라이브러리를 제안하거나 간단한 JS 구현이 아닌 설명만 제공하는 것 같습니다.

내용이 풍부해서 설명을 드리지 않겠습니다.구현 방법은 다음과 같습니다.

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}

조정이 필요할 수 있습니다(예: 처음에는 콜백이 즉시 호출되지 않음).

동작의 차이를 확인합니다(창 크기 조정 시도).

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}

var cellDefault  = document.querySelector("#cellDefault div");
var cellThrottle = document.querySelector("#cellThrottle div");
var cellDebounce = document.querySelector("#cellDebounce div");

window.addEventListener("resize", function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellDefault.appendChild(span);
    cellDefault.scrollTop = cellDefault.scrollHeight;
});

window.addEventListener("resize", throttle(function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellThrottle.appendChild(span);
    cellThrottle.scrollTop = cellThrottle.scrollHeight;
}, 500));

window.addEventListener("resize", debounce(function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellDebounce.appendChild(span);
    cellDebounce.scrollTop = cellDebounce.scrollHeight;
}, 500));
table {
    border-collapse: collapse;
    margin: 10px;
}
table td {
    border: 1px solid silver;
    padding: 5px;
}
table tr:last-child td div {
    width: 60px;
    height: 200px;
    overflow: auto;
}
table tr:last-child td span {
    display: block;
}
<table>
    <tr>
        <td>default</td>
        <td>throttle</td>
        <td>debounce</td>
    </tr>
    <tr>
        <td id="cellDefault">
            <div></div>
        </td>
        <td id="cellThrottle">
            <div></div>
        </td>
        <td id="cellDebounce">
            <div></div>
        </td>
    </tr>
</table>

JSFiddle

9LOC에서 ES6에 스로틀 기능을 구현한 방법은 다음과 같습니다. 도움이 되기를 바랍니다.

function throttle(func, delay) {
  let timeout = null
  return function(...args) {
    if (!timeout) {
      timeout = setTimeout(() => {
        func.call(this, ...args)
        timeout = null
      }, delay)
    }
  }
}

링크를 클릭하면 작동 방식을 확인할 수 있습니다.

저는 여기서 "js의 단순한 스로틀"이라고 하기에는 너무 복잡한 답변을 많이 봤습니다.

대부분의 단순한 답변은 실행을 다음 간격으로 미루는 대신 "조정 중"인 호출을 무시합니다.

다음은 "In throttle" 호출도 처리하는 간단한 구현 방법입니다.

const throttle = (func, limit) => {
  let lastFunc;
  let lastRan = Date.now() - (limit + 1); //enforces a negative value on first run
  return function(...args) {
    const context = this;
    clearTimeout(lastFunc);
    lastFunc = setTimeout(() => {
      func.apply(context, args);
      lastRan = Date.now();
    }, limit - (Date.now() - lastRan)); //negative values execute immediately
  }
}

이는 단순한 디바운스의 경우와 거의 동일한 구현입니다.함수가 마지막으로 실행된 시점을 추적해야 하는 타임아웃 지연에 대한 계산을 추가할 뿐입니다.아래 참조:

const debounce = (func, limit) => {
  let lastFunc;
  return function(...args) {
    const context = this;
    clearTimeout(lastFunc);
    lastFunc = setTimeout(() => {
      func.apply(context, args)
    }, limit); //no calc here, just use limit
  }
}

ES6의 간단한 솔루션.코데펜 데모

const handleOnClick = () => {
  console.log("hello")
}

const throttle = (func, delay) => {
  let timeout = null;

  return function (...args) {
    if (timeout === null) {
      func.apply(this, args);
      
      timeout = setTimeout(() => {
        timeout = null;
      }, delay)
    }
  }
}

document.querySelector("#button").addEventListener("click", throttle(handleOnClick, 500))
<button type="button" id="button">Click me</button>

다음은 제 자신의 비카스 게시물입니다.

throttle: function (callback, limit, time) {
    var calledCount = 0;
    var timeout = null;

    return function () {
        if (limit > calledCount) {
            calledCount++;
            callback(); 
        }
        if (!timeout) {
            timeout = setTimeout(function () {
                calledCount = 0
                timeout = null;
            }, time);
        }
    };
}

저는 그것을 사용하는 것을 발견합니다.setInterval좋은 생각이 아닙니다.

선행 및 후행 호출의 경우:

const throttle = (fn, ms) => {
  let locked = false

  return function () {
    if (!locked) {
      locked = true
      fn.apply(this, arguments)

      setTimeout(() => {
        fn.apply(this, arguments)
        locked = false
      }, ms)
    }
  }
}

테스트 케이스:

function log({ gender, address }) {
  console.log({
    name: this.name,
    gender,
    address,
  })
}

const jack = {
  name: 'Jack',
  log: throttle(log, 3000),
}

Array.from({ length: 5 }, () => jack.log({ gender: 'Male', address: 'LA' }))

조절 기능이 있는 npm 패키지를 만들었습니다.

npm 설치 기능-스캐플러

스로틀 앤 큐

최대 W 밀리초마다 호출할 수 있는 함수 버전을 반환합니다. 여기서 W는 대기합니다.W보다 더 자주 발생하는 펑에 대한 호출은 매 Wms 호출되도록 대기열에 들어갑니다.

조절된 업데이트

최대 W 밀리초마다 호출할 수 있는 함수 버전을 반환합니다. 여기서 W는 대기합니다.W보다 더 자주 발생하는 호출의 경우 마지막 호출이 호출됩니다(마지막으로 우선).

목을 조르다

는 최대 W 밀리초마다 함수가 호출되도록 제한합니다. 여기서 W는 대기합니다.W를 통한 호출이 삭제됨

이 목적에 맞는 도서관이 있는데, 엠버의 Backburner.js입니다.

https://github.com/BackburnerJS/

그렇게 쓰실 거예요.

var backburner = new Backburner(["task"]); //You need a name for your tasks

function saySomething(words) {
  backburner.throttle("task", console.log.bind(console, words)
  }, 1000);
}


function mainTask() {
  "This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}

backburner.run(mainTask)

이 스로틀 기능은 ES6를 기반으로 합니다.콜백 함수는 인수(args)를 사용하지만, 여전히 스로틀 함수와 함께 작동합니다.앱의 필요에 따라 지연 시간을 자유롭게 사용자 정의하십시오. 개발 모드에는 100ms당 1회가 사용되며, 이벤트 "on input"은 사용 빈도가 높은 예에 불과합니다.

const callback = (...args) => {
  console.count('callback throttled with arguments:', args);
};

throttle = (callback, limit) => {
  let timeoutHandler = 'null'

  return (...args) => {
    if (timeoutHandler === 'null') {
      timeoutHandler = setTimeout(() => {            
        callback(...args)
        timeoutHandler = 'null'
      }, limit)
    }
  }
}

window.addEventListener('oninput', throttle(callback, 100));

추신: @Anshul이 설명한 바와 같이, 조절은 시간이 지남에 따라 함수를 호출할 수 있는 최대 횟수를 강제합니다."100밀리초마다 최대 한 번 이 함수를 실행합니다."에서와 같이.

아래 예제에서 단추를 여러 번 클릭해 보십시오.myFunc기능은 3초에 한번만 실행됩니다.함수를throttle실행할 함수 및 지연과 함께 전달됩니다.폐쇄를 반환합니다. 폐쇄는 다음에 저장됩니다.obj.throttleFunc. 그 이후로obj.throttleFunc폐쇄, 값 저장isRunning그 안에 유지됩니다.

function throttle(func, delay) {
  let isRunning;
  return function(...args) {
    let context = this;        // store the context of the object that owns this function
    if(!isRunning) {
      isRunning = true;
      func.apply(context,args) // execute the function with the context of the object that owns it
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc(param) {
  console.log(`Called ${this.name} at ${param}th second`);
}

let obj = {
  name: "THROTTLED FUNCTION ",
  throttleFunc: throttle(myFunc, 3000)
}

function handleClick() {
  obj.throttleFunc(new Date().getSeconds());
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
    <button onclick="handleClick()">Click me</button>


컨텍스트나 인수를 전달하지 않으려면 다음과 같은 간단한 버전이 필요합니다.

function throttle(func, delay) {
  let isRunning;
  return function() {
    if(!isRunning) {
      isRunning = true;
      func()
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc() {
  console.log('Called');
}


let throttleFunc = throttle(myFunc, 3000);

function handleClick() {
  throttleFunc();
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
<button onclick="handleClick()">Click me</button>

호출할 함수가 1개밖에 없을 때 간단한 해결책도 제안하고 싶습니다(예: 검색).

여기 내가 내 프로젝트에서 한 일이 있습니다.

let throttle;

function search() {
    if (throttle) {
      clearTimeout(throttle);
    }
    throttle = setTimeout(() => {
      sendSearchReq(str)
    }, 500);
  }

입력 변경 이벤트에서 검색이 호출됩니다.

function throttle(targetFunc, delay){
  let lastFunc;
  let lastTime;

  return function(){
    const _this = this;
    const args = arguments;

    if(!lastTime){
      targetFunc.apply(_this, args);
      lastTime = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function(){
        targetFunc.apply(_this, args);
        lastTime = Date.now();
      }, delay - (Date.now() - lastTime));
    }
  }
}

사용해 보십시오.

window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));

코드 샌드박스

const { now } = Date;

export default function throttle(func, frameDuration) {
  let timeout = null;
  let latest;
  const epoch = now();

  function getDurationToNextFrame() {
    const elapsed = now() - epoch;
    const durationSinceLastFrame = elapsed % frameDuration;
    return frameDuration - durationSinceLastFrame;
  }

  function throttled(...args) {
    latest = () => {
      func.apply(this, args);
    };
    if (!timeout) {
      timeout = setTimeout(() => {
        latest();
        timeout = null;
      }, getDurationToNextFrame());
    }
  }

  return throttled;
}

단순 스로틀 기능 -

참고 - 버튼을 계속 클릭하면 처음에 클릭할 때 콘솔 로그가 표시되고 5초마다 계속 클릭할 때까지 표시됩니다.

HTML -

<button id='myid'>Click me</button>

자바스크립트 -

const throttle = (fn, delay) => {
  let lastTime = 0;
  return (...args) => {
      const currentTime = new Date().getTime();
      if((currentTime - lastTime) < delay) {
        return;
      };
      lastTime = currentTime;
      return fn(...args);
  }
};

document.getElementById('myid').addEventListener('click', throttle((e) => {
  console.log('I am clicked');
}, 5000));

깃발을 사용해 구현할 수도 있습니다.

var expensive = function(){
    console.log("expensive functionnns");
}

window.addEventListener("resize", throttle(expensive, 500))

function throttle(expensiveFun, limit){
    let flag = true;
    return function(){
        let context = this;
        let args = arguments;
        if(flag){
            expensiveFun.apply(context, args);
            flag = false;
            setTimeout(function(){
                flag = true;
            }, limit);
        }
    }
}

@clément-prevost 답변의 약간 현대화되고 단순화된 버전은 다음과 같습니다.

function throttle(func, wait, options = {}) {
  let timeout = null;
  let previous = 0;

  const later = (...args) => {
    previous = options.leading === false ? 0 : Date.now();
    func(...args);
  };

  return (...args) => {
    const now = Date.now();

    if (!previous && options.leading === false) {
      previous = now;
    }

    const remaining = wait - (now - previous);

    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      func(...args);
    } else if (options.trailing !== false) {
      clearTimeout(timeout);
      timeout = setTimeout(() => later(...args), remaining);
    }
  };
}

function myFunc(a) {
  console.log(`Log: ${a} ${this.val}`);
}

const myFuncThrottled = throttle(myFunc.bind({val: 42}), 1234, {leading: true, trailing: true})

myFuncThrottled(1)
myFuncThrottled(2)
myFuncThrottled(3)

function throttle(CB,ms=300,Id='Identifier for the callback(CB)'){
  Id = Id || ""+CB
  var N = throttle.N = throttle.N || {};  // Static variable N to store all callbacks ids and their status 
  if( N[Id] ) return;             // already in the queue to run 
  N[Id] = 1;                      // add it the queue 
  setTimeout(()=>{
    N[Id] = 0;                    // remove it from the queue
    CB();                         // finally call the function 
  }, ms);
}



for(var i=0;i<100;i++){
   throttle(e=>console.log("Hi1"),1e3,'F1');
}

// will only  output : Hi1
// this function guarantee the callback to run at least once 

여기에 이미 몇 가지 훌륭한 솔루션이 있지만, 저는 각 함수 호출에 제공되는 마지막 인수와 함께 후행(및 선택적으로 선행) 실행 기능이 있는 최신 버전을 찾고 있었습니다.

const throttle = (fn, wait=500, leading=true) => {
  let prev, timeout, lastargs;
  return (...args) => {
    lastargs = args;
    if (timeout) return;
    timeout = setTimeout(() => {
      timeout = null;
      prev = Date.now();
      // let's do this ... we'll release the stored args as we pass them through
      fn.apply(this, lastargs.splice(0, lastargs.length));
      // some fancy timing logic to allow leading / sub-offset waiting periods
    }, leading ? prev && Math.max(0, wait - Date.now() + prev) || 0 : wait);
  };
}

용도:

x = throttle((...args) => console.log(...args));
let n = 0;
x(++n, 'boom');
x(++n, 'boom');
x(++n, 'boom');

하나씩 정의하는 함수가 하나 이상 있으면 유지할 수 없기 때문에 도우미 클래스를 사용하여 각각의 값을 유지하는 것이 좋습니다.

class slowDown {
    constructor(cb,timeGap){
        this.last = 0
        this.run = function(){
            let current = Date.now(),
                shouldRun = (current - this.last) >= timeGap
            if(shouldRun){
                cb(current - this.last)
                this.last = current
            }            
        }
    }
}

// example use
const press = new slowDown(timeElapsed => {
    // define function here which you wanted to slow down
    console.log("pressed after " + timeElapsed + " ms")
},750)

window.addEventListener("keydown",()=>{
    press.run()
})

아래는 13 LOC에서 생각할 수 있는 가장 간단한 스로틀입니다.함수를 호출할 때마다 시간 초과가 발생하고 이전 함수를 취소합니다.원래 함수는 예상대로 적절한 컨텍스트와 인수로 호출됩니다.

function throttle(fn, delay) {
  var timeout = null;

  return function throttledFn() {
    window.clearTimeout(timeout);
    var ctx = this;
    var args = Array.prototype.slice.call(arguments);

    timeout = window.setTimeout(function callThrottledFn() {
      fn.apply(ctx, args);
    }, delay);
  }
}

// try it out!
window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));

언급URL : https://stackoverflow.com/questions/27078285/simple-throttle-in-javascript