How do I reuse this JavaScript timeout closure?

2020-07-27 02:42发布

问题:

I found the following piece of JavaScript code (maybe here at Stack Overflow?) for implementing a timeout:

var delay = (function() {
  var timer = 0;
  return function(callback, ms) {
    clearTimeout(timer);
    timer = setTimeout(callback, ms);
  };
})();

I'm new to JavaScript, so I'm still trying to wrap my head around closures. If I call delay(firstCallback, 200) in one place and then delay(secondCallback, 200) right after, the first timeout callback is cleared, while the second callback executes successfully.

How do I reuse delay in different instances without overwriting other instances? (Not sure if those terms are correct, but hopefully it gets my point across.) So, in the above example, I want both callbacks to execute.

Thanks for your help!

EDIT: As a practical example, I'm trying to buffer keypress events on an input field, so that the callback is only executed after no key has been pressed for 200ms. But I have multiple input fields, and currently the buffer breaks when two input fields have keypress events in quick succession.

回答1:

To reuse this, it is easier to get rid of the anonymous function and use it as a generator.

var createDelayManager = function() {
  var timer = 0;
  return function(callback, ms) {
     clearTimeout(timer);
     timer = setTimeout(callback, ms);
  };
}


var delayManagerOne = createDelayManager();
delayManagerOne(firstCallback, 200);

var delayManagerTwo = createDelayManager();
delayManagerTwo(secondCallback, 200);

Here is a working fiddle: http://jsfiddle.net/HJrM7/

But, It is worth noting, that the point of this closure is to prevent multiple callbacks from getting stacked against some object. It is a really nice closure though that enables you to make sure that the last event triggered will be the one that gets acted on. I use this technique a lot to prevent flickering, or unwanted mouse out events during ie redraws.



回答2:

You are looking for

Delay = function(callback, ms) {
    this.callback = callback;
    this.ms = ms;
};

Delay.prototype = {
    timer: -1,

    restart: function() {
        if (this.timer != -1) clearTimeout(this.timer);
        this.timer = setTimeout(this.callback, this.ms);
    }
};

var delay1 = new Delay(callback1, 100);
var delay2 = new Delay(callback2, 100);

// on some event1
delay1.restart();

// on some event2
delay2.restart();

Example on jsfiddle: http://jsfiddle.net/TF9Tw/1/



回答3:

The closure holds state that persists between calls of the inner function. In this case it is used so there is only one timeout at a time. If you want multiple timeouts available at a time you may just write

setTimeout(callback, ms);

edit:

For your example, the best solution would be to make an object like this

function DelayManager(){}
DelayManager.prototype.timer = 0;
DelayManager.prototype.delay = function(callback, ms) {
  clearTimeout(this.timer);             
  this.timer = setTimeout(callback, ms);   
}
someDelayManager = new DelayManager();

Where instead of someDelayManager you would use a member variable of some object unique per input element. To add a delay you would then call.

someDelayManager.delay(callback, ms);