How do I kill a setInterval()/setTimout() if I lose the calling object?

。_饼干妹妹 提交于 2020-02-08 03:09:44

问题


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...

  1. Can anyone explain?

  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!