8

私は JavaScript をいじっていましたが、奇妙な動作に気づきました (少なくとも私にとっては奇妙です. . .)

だから私はここに行くSSCCEをやった:

「myDiv」という名前のdivがあります

function changeText(text){
    document.getElementById("myDiv").innerHTML=text;
}

function recursiveCall(counter){
    if(counter){
        setTimeout(function(){
            recursiveCall(--counter);
            changeText(counter);
        },750);
    }
}

recursiveCall(10);

実際の例: http://jsfiddle.net/T645X/

changeText(counter);だから私はdivのテキストを変更しています.実際に変更するメソッドを呼び出す前に再帰呼び出しが行われるため、0から9になるはずだと思っていましたが、テキストが9から0になります.テキスト。

4

3 に答える 3

8

この関数には、非同期のタイムアウトが含まれています。

setTimeout(function(){
    recursiveCall(--counter);// calls the next function, which will call the next 
                             // and print in a timeout
    changeText(counter);  // print
},750);

再帰呼び出しがタイムアウトになる前に、テキストが変更されます。

必要に応じて、タイムアウトの外側から印刷呼び出しを移動できます。これにより、次のような期待される動作が得られます。

function recursiveCall(counter){
    if(counter){
        recursiveCall(--counter);            
        setTimeout(function(){
            changeText(counter);
        },750);
    }
}

(ただし、ここでは印刷のタイミングがずれていないことに注意してください。タイマーを最初に設定したという理由だけで、最初に印刷されると仮定して、未定義の動作に多少依存しています)

それでも遅れて印刷したい場合は、関数にそれが完了したことを伝えることができます。再帰は最初に行われますが、各レベルはその上のレベルにそれが完了したことを伝えます:

function recursiveCall(counter,done){
    if(counter){
        // note how recursion is done before the timeouts
        recursiveCall(counter-1,function(){ //note the function
            setTimeout(function(){          //When I'm done, change the text and let the 
                changeText(counter-1);      //next one know it's its turn.
                done(); // notify the next in line.
            },750);
        });
    }else{
        done(); //If I'm the end condition, start working.
    }
}

これを実装するフィドルを次に示します。

于 2013-07-26T22:47:56.513 に答える
4

厳密に言えば、ここには再帰はありません

の呼び出しsetTimeoutは、スケジュールされたタイマー イベントのリストにコールバックを追加するだけです。

ほとんどの場合、ブラウザはイベントを待っているだけで、イベントを処理 (つまり、イベント ハンドラを実行) してから、イベントの待機に戻ります。

したがって、この場合、あなたがしていることは次のとおりです。

   recursiveCall(10)
   timer event and callback added to the queue
   function exits

... waits 750 ms ...

   timer event fires, callback pulled from the queue and invoked
      -> recursiveCall(9) invoked
        ->  timer event and callback added to the queue
      -> changeText(9) invoked
   callback function exits

... waits 750 ms ...

   timer event fires, callback pulled from the queue and invoked
      -> recursiveCall(8) invoked
        ->  timer event and callback added to the queue
      -> changeText(8) invoked
   callback function exits

and so on...

私はこれを疑似再帰と呼んでいますが、これは古典的な再帰にいくらか似ていますが、各呼び出しは同じ「スタック フレーム」で開始されるためです。つまり、スタック トレースを要求した場合、通常は 1 つ (または場合によっては 2 つ) のインスタンスしか存在しません。一度にrecursiveCall現在の。

于 2013-07-26T23:09:44.123 に答える
2

理解しておくべきことの 1 つは、そもそも再帰ではないということです。関数に適切な exit 句がなかった場合、これは、吹き飛ばされたスタックに遭遇することなく永遠に続く可能性があります。

その理由は、渡された関数は現在の実行コンテキストのsetTimeout()で実行されるためです。つまり、コードは関数から「抜け出します」。

それらの間に 750 ミリ秒の再帰呼び出しが必要な場合は、次のようにすることができます。

function recursiveCall(counter, fn)
{
    if (counter) {
        recursiveCall(--counter, function() {
            changeText(counter);
            setTimeout(fn, 750);
        });
    } else if (fn) {
        fn(); // start chain backwards
    }
}

再帰するとコールバックのチェーンが作成され、exit 句がチェーン全体の動きを後方に設定します:)

于 2013-07-26T23:07:17.280 に答える