问题
I'm working on a rather large app, I need to call a function over an over again while a key is being pressed, at a specific interval. There's parts of the app that I can't edit, but it's replacing my .onkeyup() listeners and sometimes the interval is just left there forever. What I really want is for the interval to stop when the object gets destroyed, reassigned, etc... After setInterval(), bindings, closures, I made this to try something out and now I am even more confused:
function myInterval(){
this.name = 'Paul'
that=this;
this.go = function go(){
if(that){
this.interval = setTimeout(function(){
console.log(that.name);
that.go();
},100);
};
console.log(that.name);
};
};
periodic = new myInterval();
periodic.go();
setTimeout(function(){
periodic.name='George';
}, 200);
setTimeout(function(){
periodic.name ='John';
periodic.go = '';
periodic.name = 'Ringo'
}, 300);
Output:
Paul
Paul
Paul
George
George
Ringo
> Uncaught TypeError: Property 'go' of object #<myInterval> is not a function
So, reassigning the function works, but if I replace
periodic.go = '';
with
periodic = '';
I get
Paul
Paul
Paul
George
George
John
John
...
And so on forverer; The interval never stops...
Can anyone explain?
Is there a common, elegant solution to ensuring I don't go dropping running intervals into the aether?
回答1:
The interval never stops...Can anyone explain?
Variables trapped in closure scope is the reason for that, the following code will use variable o in same scope that sets o to null.
Note: I prefer to use closure creators to limit scope and prevent other surprises. The following will create closures on they fly to keep code simple.
var o = {};
setTimeout(function(){//closure accessing o directly
console.log("why is o null here:",o);
},100);
o=null;
The following code will use o as passed o, passed o is now trapped in the created closure scope and setting o to null does not affect passed o. Mutating o (o.something=22) will affect passed o. (google "javascript by reference by value" for references)
var o = {};
setTimeout((function(o){
return function(){//closure accessing passed o
console.log("why is o not here:",o);
};
}(o)),100);
o=null;
To solve a common problem in loops creating closurs you pass the variable (i) to a function that returns a closure
for(var i = 0;i<10;i++){
setTimeout((function(i){
return function(){//not using i in the for loop but the passed one
console.log("and i is:",i);//0 to 9
};
}(i)),100);
}
Because having the i variable in the same scope as the closure will give you unwanted results:
for(var i = 0;i<10;i++){
setTimeout(function(){
console.log("and i is:",i);//10 times and i is: 10
},100);
}
Why periodic.go="" works has something to do with the pass by value by reference. How that works is shown in the following code:
function test(o){
o=22;//no mutation but an assignment
}
var o = {name:"me"};
test(o);
console.log(o);//Object { name="me" }
function test2(o){
o.name="you";//mutates
}
test2(o);
console.log(o);//Object { name="you"}
How to solve
I've changed your code a little to take advantage of protype for shared members (the go function) and create closures to make sure the scope of the closure is limited to what you actually need to be in there.
For more details read the introduction to constructor function and the this variable.
function MyInterval(){//capitalize constructor
this.name = 'Paul';
this.doContinue = true;
this.timeoutid = false;
};
MyInterval.prototype.closures ={//creates closures with limited scope
//closure for the go function setting the invoking object
go:function(me){
return function(){
console.log("In go, name is:",me.name);
me.go();
//de reference "me", we no longer need it
// can't do this in a setInterval though
me=null;
};
}
}
MyInterval.prototype.go = function(){
if(this.constructor===MyInterval){
//if you were to call go multiple times
if(this.timeoutid)
clearTimeout(this.timeoutid);
//do it again if this.doContinue is true
this.timeoutid = (this.doContinue)? setTimeout(
this.closures.go(this)
//creates a closure function
,100
):false;
return;
};
console.log("this is not the correct value:",this);
};
//added stop, good call from shadow, now you can stop immediately
// or set doContinue to false and let it run it's course
MyInterval.prototype.stop = function(){
if(this.timeoutid)
clearTimeout(this.timeoutid);
this.timeoutid=false;
};
periodic = new MyInterval();
periodic.go();
//because the clearTimeout you can accedentally do something silly like:
periodic.go();
periodic.go();
setTimeout(function(){
periodic.name='George';
}, 150);
setTimeout(function(){
periodic.name ='John';
periodic.doContinue = false;
periodic.name = 'Ringo'
}, 250);
回答2:
var timer = setTimeout(func,100)
To clear it
if(timer)
clearTimeout(timer)
In case of intervals
var timer = setInterval(func,100)
To clear it
if(timer)
clearInterval(timer)
EX
function myInterval(){
this.name = 'Paul'
that=this;
this.go = function go(){
if(that){
this.interval = setTimeout(function(){
console.log(that.name);
that.go();
},100);
};
console.log(that.name);
};
this.stop = function(){ if(this.interval) clearInterval(this.interval);};
};
回答3:
In your code:
function myInterval(){
this.name = 'Paul'
that=this;
that becomes a global when you first call myInterval and the above line is executed, I think you mean to keep it local. Otherwise, that will reference the last instance created, which is not necessarily the "current" instance.
Anyhow, the best way to do this is to give instances a stop method. So you do:
// By convention, constructors start with a capital letter
function MyInterval() {
// Use a more suitable name than "that" to reference the instance
var interval = this;
this.name = 'Paul'
// This keeps calling itself every 100ms
this.go = function go(){
// Keep a reference to the timeout so it can be cancelled
this.timeout = setTimeout(function(){
console.log('setTimeout: ' + interval.name);
interval.go();
}, 100);
console.log('Go: ' + interval.name);
};
// Stop the timeout
this.stop = function() {
if (interval.timeout) {
clearTimeout(interval.timeout);
}
};
};
var periodic = new MyInterval();
// In 100 ms, it will log its name
periodic.go();
// In 200 ms its name will be changed
setTimeout(function(){
periodic.name='George';
}, 200);
// In 500 ms its name will be changed and it will be cancelled.
setTimeout(function(){
periodic.name ='John';
// call stop here
periodic.stop();
periodic.name = 'Ringo'
}, 500);
来源:https://stackoverflow.com/questions/19970399/how-do-i-kill-a-setinterval-settimout-if-i-lose-the-calling-object