11

私は(友人から)タイマー(毎秒行を書き込む無限のもの)を構築するように頼まれましたが、setInterval.

私はそれを解決しました:

var i = 0;

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

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

    go();

そしてそれは働いています。

問題は、メモリ不足になるのではないかと心配していることです。実際には再帰が作成され、しばらくすると (1 週間か何か)、プロセスが大量のメモリを消費します。(スタックの割り当てが解除されることはありません)

メモリをあまり消費しないようにコードを変更するにはどうすればよいですか?

4

3 に答える 3

14

再帰ではない

再帰のように見えるかもしれませんが、setTimeout は再帰を作成しません。

setTimeout が機能する方法は、すぐに戻ることです。したがって、への呼び出しはk、そのスタックの割り当てが解除された状態ですぐに終了します。

タイムアウトが実際に発生し、への呼び出しgoが再度発生した場合、それは前の呼び出しのポイントからではなくk、グローバル スコープからのものです*。

* 注: ここでは、ECMAScript 仕様で定義されているスコープの厳密な意味を使用していません。私が言いたいのは、 への呼び出しkは、プレーンなタグで書いたかのように行われるということです<script></script>。つまり、他の関数呼び出しの外側です。

閉店の懸念について

あなたの特定のケースでは、関数によって作成されたクロージャーに実際に囲まれているものはほとんどありませんk。唯一の重要なクロージャーは、引数cbおよびへの参照myIdです。それでも、約 1 秒間しか持続しません。

 #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

この行は誤りです:

それは実際に再帰を作成し、しばらくすると(1週間か何か)-プロセスは多くのメモリを消費します。(スタックの割り当てが解除されることはありません)

関数が完全に終了してから再度呼び出されるため、再帰は作成されません。

再帰スタックが互いに重なり合う

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 に答える