Infinite Timer Loop with javascript ( no setInterv

2019-02-02 19:26发布

问题:

I was asked (by a friend) to build a timer (infinite one which writes a line every second), but without setInterval.

I solved it with :

var i = 0;

    function k(myId, cb)
    {
        setTimeout(function ()
        {
            console.log(myId);
            cb();
        }, 1000);
    }

    function go()
    {
        i++;
        k(i, go);
    }

    go();

And it is working.

The problem is that I'm afraid there's gonna be a memory pressure. It actually creates a recursion and after a while (week or something) - the process will consume much memory. (the stack is never deallocated)

How can I change my code in order not to be much memory consume?

回答1:

It's not recursion

It may look like recursion, but setTimeout does not create recursion.

The way setTimeout works is that it returns immediately. So the call to k ends immediately with its stack deallocated.

When the timeout actually happens and the call to go happens again it is not from the point of the previous call to k but from the global scope*.

* Note: I'm not using the strict meaning of scope as defined in ECMAScript spec here. What I mean is the call to k will be made as if you have written it in a plain <script></script> tag: that is to say, outside of any other function calls.

Regarding your concern over the closure

In your specific case, there is very little that's actually enclosed in the closure created by the k function. The only significant closure is the reference to the arguments cb and myId. And even then it only lasts for approximately one second:

 #1   function k(myId, cb) {
 #2        setTimeout(function(){
 #3            console.log(myId); // there is a closure here to myId
 #4            cb();              // and another one for cb
 #5
             /* But at this point in the function, setTimeout ends
             * and as the function returns, there are no remaining
             * references to either "cb" or "myId" accessible
             * anywhere else. Which means that the GC can immediately
             * free them (though in reality the GC may run a bit later)
             */
  #6       }, 1000); // So one second is roughly the longest the closure lasts
    }

Could be simpler

I should note that your code is fairly convoluted. It can be written simpler, and without using closures at all (minus the global variable i) if you simply write it like this:

// Simpler, does exactly the same thing:
var i = 0;
function go () {
    console.log(i);
    i++;
    setTimeout(go, 1000); // callback
}
go();


回答2:

This line is false:

It actually creates a recursion and after a while (week or something) - the process will consume much memory. ( the stack is never deallocated)

It does not create recursion, because the function exits completely and is then called again.

Recursion stacks on top of each other

function a() {a()}; // function calls itself until a stack overflow.

The stack looks like this

a()
  a()
    a()
      a() ... until a crash.

With setTimeout, you execute a function. That function sets up a event for the function to run again -- but here is the important difference: The function exits, completely, and is gone[1]. THEN it is called again.

Execution wise, it isn't much different from doing this:

function a() {console.log("I am called");}

a(); // Call the function;
a(); // Call the function again
a(); // Call the function again

setTimeout just gives the browser a chance to 'breath' if you will. A chance for the screen to update, other events to process. It doesn't, to use the right terminology, block the browser.



回答3:

This does not create a memory leak.

In fact, it's a pretty commonly used concept. Usually it appears in this form:

setTimeout(function next() {

    // Do something...

    // Have the function set another timeout to call itself later.
    setTimeout(next, 10);

}, 10);

When you want to check for something often (here every 10 milliseconds), it can be better to use this pattern instead of setInterval because it can improve your page performance. For instance, if your function takes more than 10 milliseconds to execute, and you use setInterval(f, 10) then it will continually be being called. However, if you use the setTimeout pattern above, it will at least make sure the processor gets a 10 millisecond break between each call, no matter how long your function takes to execute.

See this video (starting at 7:46) from Paul Irish for more information about this pattern.