I use the following code to create countdowns in Javascript. n is the number of times to repeat, freq is the number of milliseconds to wait before executing, funN is a function to call on each iteration (typically a function that updates part of the DOM) and funDone is the function to call when the countdown is complete.
我使用以下代码在Javascript中创建倒计时。 n是重复次数,freq是执行前等待的毫秒数,funN是每次迭代时调用的函数(通常是更新DOM部分的函数),funDone是倒计时时调用的函数完成了。
function timer(n, freq, funN, funDone)
{
if(n == 0){
funDone();
}else{
setTimeout(function(){funN(n-1); timer(n-1, freq, funN, funDone);}, freq);
}
}
It can be called like so:
它可以像这样调用:
timer(10,
1000, /* 1 second */
function(n){console.log("(A) Counting: "+n);},
function() {console.log("(A) Done!");}
);
timer(10,
500,
function(n){console.log("(B) Counting: "+n);},
function() {console.log("(B) Done!");}
);
The advantage of this is that I can call timer() as many times as I want without worrying about global variables etc. Is there a better way to do this? Is there a clean way to make setInterval stop after a certain number of calls (without using global variables)? This code also creates a new lambda function with each call to setTimeout which seems like it could be problematic for large countdowns (I'm not sure how javascript's garbage collector handles this).
这样做的好处是我可以根据需要多次调用timer()而不用担心全局变量等。有更好的方法吗?是否有一种干净的方法使setInterval在一定数量的调用后停止(不使用全局变量)?这段代码也创建了一个新的lambda函数,每次调用setTimeout,这似乎对大型倒计时有问题(我不确定javascript的垃圾收集器如何处理这个)。
Is there a better way to do this? Thanks.
有一个更好的方法吗?谢谢。
3 个解决方案
#1
I'd create an object that receives a counter and receives a function pointer to execute, something akin to the following pseudo code:
我创建了一个接收计数器并接收要执行的函数指针的对象,类似于以下伪代码:
TimedIteration = function(interval, iterations, methodToRun, completedMethod){
var counter = iterations;
var timerElapsed = methodToRun; //Link to timedMethod() method
var completed = callbackMethod;
onTimerElapsed = function(){
if (timerElapsed != null)
timerElapsed();
}
onComplete = function(){
if (completed != null)
completed();
}
timedMethod = function(){
if (counter != null)
if (counter > 0) {
setTimeOut(interval, onTimerElapsed);
counter--;
}
else
onComplete();
this = null;
}
}
if ((counter != null)&&(counter > 0)){
//Trip the initial iteration...
setTimeOut(interval, timedMethod);
counter--;
}
}
obviously this is pseudo code, I've not tested it in an IDE and syntactically I'm not sure if it'll work as is [I'd be astonished if it does], but basically what you're doing is you're creating a wrapper object that receives a time interval, a number of iterations and a method to run upon the timer elapsed.
显然这是伪代码,我没有在IDE中进行测试,从语法上来说我不确定它是否能正常工作[如果它确实会让我感到惊讶],但基本上你正在做的就是你'重新创建一个包装器对象,它接收一个时间间隔,多次迭代以及在计时器运行时运行的方法。
You'd then call this on your method to run like so:
然后你可以在你的方法上调用它来像这样运行:
function myMethod(){
doSomething();
}
function doWhenComplete(){
doSomethingElse();
}
new TimedIteration(1000, 10, myMethod, doWhenComplete);
#2
This is basically the same idea as @balabaster, but it is tested, uses prototype, and has a little more flexible interface.
这与@balabaster基本相同,但它经过测试,使用原型,并且具有更灵活的界面。
var CountDownTimer = function(callback,n,interval) {
this.initialize(callback,n,interval);
}
CountDownTimer.prototype = {
_times : 0,
_interval: 1000,
_callback: null,
constructor: CountDownTimer,
initialize: function(callback,n,interval) {
this._callback = callback;
this.setTimes(n);
this.setInterval(interval);
},
setTimes: function(n) {
if (n)
this._times = n
else
this._times = 0;
},
setInterval: function(interval) {
if (interval)
this._interval = interval
else
this._interval = 1000;
},
start: function() {
this._handleExpiration(this,this._times);
},
_handleExpiration: function(timer,counter) {
if (counter > 0) {
if (timer._callback) timer._callback(counter);
setTimeout( function() {
timer._handleExpiration(timer,counter-1);
},
timer._interval
);
}
}
};
var timer = new CountDownTimer(function(i) { alert(i); },10);
...
<input type='button' value='Start Timer' onclick='timer.start();' />
#3
I like your original solution better than the proposed alternatives, so I just changed it to not create a new function for every iteration (and the argument of fun()
is now the value before decrement - change if needed...)
我比你提出的替代方案更喜欢你的原始解决方案,所以我只是将它改为不为每次迭代创建一个新函数(而fun()的参数现在是递减之前的值 - 如果需要改变......)
function timer(n, delay, fun, callback) {
setTimeout(
function() {
fun(n);
if(n-- > 0) setTimeout(arguments.callee, delay);
else if(callback) callback();
},
delay);
}
#1
I'd create an object that receives a counter and receives a function pointer to execute, something akin to the following pseudo code:
我创建了一个接收计数器并接收要执行的函数指针的对象,类似于以下伪代码:
TimedIteration = function(interval, iterations, methodToRun, completedMethod){
var counter = iterations;
var timerElapsed = methodToRun; //Link to timedMethod() method
var completed = callbackMethod;
onTimerElapsed = function(){
if (timerElapsed != null)
timerElapsed();
}
onComplete = function(){
if (completed != null)
completed();
}
timedMethod = function(){
if (counter != null)
if (counter > 0) {
setTimeOut(interval, onTimerElapsed);
counter--;
}
else
onComplete();
this = null;
}
}
if ((counter != null)&&(counter > 0)){
//Trip the initial iteration...
setTimeOut(interval, timedMethod);
counter--;
}
}
obviously this is pseudo code, I've not tested it in an IDE and syntactically I'm not sure if it'll work as is [I'd be astonished if it does], but basically what you're doing is you're creating a wrapper object that receives a time interval, a number of iterations and a method to run upon the timer elapsed.
显然这是伪代码,我没有在IDE中进行测试,从语法上来说我不确定它是否能正常工作[如果它确实会让我感到惊讶],但基本上你正在做的就是你'重新创建一个包装器对象,它接收一个时间间隔,多次迭代以及在计时器运行时运行的方法。
You'd then call this on your method to run like so:
然后你可以在你的方法上调用它来像这样运行:
function myMethod(){
doSomething();
}
function doWhenComplete(){
doSomethingElse();
}
new TimedIteration(1000, 10, myMethod, doWhenComplete);
#2
This is basically the same idea as @balabaster, but it is tested, uses prototype, and has a little more flexible interface.
这与@balabaster基本相同,但它经过测试,使用原型,并且具有更灵活的界面。
var CountDownTimer = function(callback,n,interval) {
this.initialize(callback,n,interval);
}
CountDownTimer.prototype = {
_times : 0,
_interval: 1000,
_callback: null,
constructor: CountDownTimer,
initialize: function(callback,n,interval) {
this._callback = callback;
this.setTimes(n);
this.setInterval(interval);
},
setTimes: function(n) {
if (n)
this._times = n
else
this._times = 0;
},
setInterval: function(interval) {
if (interval)
this._interval = interval
else
this._interval = 1000;
},
start: function() {
this._handleExpiration(this,this._times);
},
_handleExpiration: function(timer,counter) {
if (counter > 0) {
if (timer._callback) timer._callback(counter);
setTimeout( function() {
timer._handleExpiration(timer,counter-1);
},
timer._interval
);
}
}
};
var timer = new CountDownTimer(function(i) { alert(i); },10);
...
<input type='button' value='Start Timer' onclick='timer.start();' />
#3
I like your original solution better than the proposed alternatives, so I just changed it to not create a new function for every iteration (and the argument of fun()
is now the value before decrement - change if needed...)
我比你提出的替代方案更喜欢你的原始解决方案,所以我只是将它改为不为每次迭代创建一个新函数(而fun()的参数现在是递减之前的值 - 如果需要改变......)
function timer(n, delay, fun, callback) {
setTimeout(
function() {
fun(n);
if(n-- > 0) setTimeout(arguments.callee, delay);
else if(callback) callback();
},
delay);
}