11

我被(一位朋友)要求建立一个计时器(无限的,每秒写一行),但没有setInterval.

解决了它:

var i = 0;

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

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

    go();

它正在工作。

问题是我担心会有记忆压力。它实际上创建了一个递归,并且在一段时间(一周或其他时间)之后 - 该过程将消耗大量内存。(堆栈永远不会被释放)

如何更改我的代码以不消耗太多内存?

4

3 回答 3

14

这不是递归

它可能看起来像递归,但 setTimeout 不会创建递归。

setTimeout 的工作方式是立即返回。所以调用k立即结束,堆栈被释放。

当超时实际发生并且go再次发生调用时,它不是来自上一次调用的点,k而是来自全局范围*。

* 注意:我没有使用 ECMAScript 规范中定义的范围的严格含义。我的意思是调用k将就像您将其写在一个普通的<script></script>标签中一样:也就是说,在任何其他函数调用之外。

关于您对关闭的担忧

k在您的特定情况下,该函数创建的闭包中实际上包含的内容很少。唯一重要的闭包是对参数cb和的引用myId。即便如此,它也只会持续大约一秒钟:

 #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
    }

可以更简单

我应该注意到您的代码相当复杂。如果你像这样简单地编写它,它可以写得更简单,并且根本不使用闭包(减去全局变量 i):

// Simpler, does exactly the same thing:
var i = 0;
function go () {
    console.log(i);
    i++;
    setTimeout(go, 1000); // callback
}
go();
于 2012-11-22T06:20:15.993 回答
7

此行是错误的:

它实际上创建了一个递归,并且在一段时间(一周或其他时间)之后 - 该过程将消耗大量内存。(堆栈永远不会被释放)

它不会创建递归,因为函数完全退出然后再次调用。

递归堆叠在彼此之上

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

堆栈看起来像这样

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

使用 setTimeout,您可以执行一个函数。该函数为函数再次运行设置了一个事件——但重要的区别在于:函数完全退出,并且消失了[1]。然后它再次被调用。

执行方面,它与这样做没有太大区别:

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

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

setTimeout如果你愿意,只是给浏览器一个“呼吸”的机会。屏幕更新的机会,处理其他事件的机会。用正确的术语来说,它不是block浏览器。

于 2012-11-22T06:24:00.767 回答
2

这不会造成内存泄漏。

事实上,这是一个非常常用的概念。通常它以这种形式出现:

setTimeout(function next() {

    // Do something...

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

}, 10);

当您想经常检查某事(这里每 10 毫秒)时,最好使用此模式,而不是setInterval因为它可以提高您的页面性能。例如,如果您的函数执行时间超过 10 毫秒,并且您使用它,setInterval(f, 10)那么它将不断被调用。但是,如果您使用setTimeout上面的模式,它至少可以确保处理器在每次调用之间获得 10 毫秒的中断,无论您的函数执行多长时间。

有关此模式的更多信息,请参阅Paul Irish 的此视频(从 7:46 开始) 。

于 2012-11-22T06:17:43.793 回答