JavaScriptのsetTimeoutについて、ちょっとだけ研究してみた。

話題のsetTimeoutについて、ちょっと調べてみた。

IT戦記殿(id:amachangさん)の記事には、大雑把に言って、ふたつのことが書いてある。

ひとつは、これ。

document.write("hoge\n");
setTimeout(function(){ document.write("fuga\n") }, 1000);
document.write("piyo\n");

普通に JavaScript を使いこなしてる人なら、hoge → piyo と表示して、 1 秒後に fuga が表示されるな。って思うはずなんです。

でも、 JavaScript を始めたばっかりの人の中には、 hoge と表示したあと 1 秒後に fuga → piyo と表示するな。って思ってる人が非常に多い。(経験的に)

IT戦記 - JavaScript を学ぶ際に一番重要なのに、誤解されがちな setTimeout 系の概念

まず、"hoge→fuga→piyo" なのか、 "hoge→piyo→fuga" なのかの話。

JavaScriptを始めたばかりの人は"hoge→(一秒)→fuga→piyo"だと誤解している場合がある、と。

まあ、これはいいと思う。この三行だけなら、"hoge→piyo→(一秒)→fuga" となる、と。

んで、ここから先には、別の話が書いてある。(「なぜか?」と「ぶっちゃけ」の間に、深い闇を感じるのは俺だけだろうか?)

それはなんの話かというと、例えば、以下のようなHTML+JavaScriptを考えてみると……

<html>
<body>
<div id="hoge">hoge </div>
<script type="text/javascript">

function mugenloop() {
  var n;
  var t = new Date();
  for (var i = 0; i < 300000; i++) {
    n++;
  }
  hoge.innerHTML = hoge.innerHTML + 
('(' + ((new Date()).getTime() - t.getTime()) + 'msec) ');

}

function showSomething(x) {
  hoge.innerHTML = hoge.innerHTML + " " + x;
  //alert(x);
}

setTimeout(function() { showSomething('fuga') }, 1);

mugenloop();

showSomething('piyo');

</script>
</body>
</html>

結果は、以下のようになる。(うちのパソコンのIE6では)

hoge (313msec) piyo fuga

これは、ちょっと意外だ。(少なくとも、俺にとっては意外だった)

setTimeoutの1ミリ秒後、つまり、mugenloopが実行されている最中にfugaが表示されるかと思ったからだ。

ここで、上記のJavaScript(+HTML)を以下のようにしてみる。

<html>
<body>
<div id="hoge">hoge </div>
<script type="text/javascript">

function mugenloop() {
  var n;
  var t = new Date();
  for (var i = 0; i < 300000; i++) {
    n++;
  }
  hoge.innerHTML = hoge.innerHTML +
 ('(' + ((new Date()).getTime() - t.getTime()) + 'msec) ');
}

function showSomething(x) {
  hoge.innerHTML = hoge.innerHTML + " " + x;
  //alert(x);
}

setTimeout(function() { showSomething('1fuga') }, 10);

mugenloop();

</script>
<script type="text/javascript">

mugenloop();

showSomething('2piyo');

</script>
<script type="text/javascript" src="test.js"></script>
</body>
</html>

違うのは、以下の三点。

  • 途中でSCRIPTタグが挿入されて、意味も無く千切れている
  • 外部JavaScriptファイル「test.js」を読み込んでいる
  • 1fuga、2piyoに修正

test.jsの中身は、以下のようになっている

mugenloop();

showSomething('3nasubi');

ここで、結果として予想されるのは、

hoge→(ループ)→2piyo→(ループ)→3nasubi→1fuga

という順番(だろうか?)

手元のパソコンでの実行結果は、「不定」であった。

  • hoge (312msec) (328msec) 2piyo 1fuga(297msec) 3nasubi
  • hoge (329msec) (312msec) 2piyo(312msec) 3nasubi 1fuga
  • hoge (329msec) 1fuga(313msec) 2piyo(328msec) 3nasubi

なぜ不定なのかは、全く理解できないが、別のSCRIPTブロックや外部読み込みJavaScriptファイルが実行される場合には、その実行が先立つsetTimeoutの「前」になることは保証できない、ということはいえるだろう。

ただ、ひとつのSCRIPTタグで括られた範囲では、setTimeoutで仕掛けられたコードは、全てのコードの実行が終わったあとに動くようだ。

(これを「保証されている」というかどうかは、甚だ疑問だが)

この先には、JavaScriptの深い闇があるような気がする。

ここで、参考までに、ちょっとGoogleで検索してみたところ、

var OKtoProceed = true;
function wait(iTime)
{ //wait for the specified time
  OKtoProceed = false;
  setTimeout('resume()', iTime);
  while(!OKtoProceed);
  return;
}

function resume()
{ OKtoProceed = true; } 

Thankyou for your response, I have tried to code as you suggested. The code works
quite well for MSIE4+, but unfortunately not for NN4.7. Well, I think I couldn't
find any other alternatives. Have you got one?

(お返事ありがとう。あなたのコードを試してみました。MSIE4+では動きましたが、NN4.7ではだめでした。うーん、私は、これ以外の手段を考えることはできないのですが、なにか他にありますか?)

Google Groups microsoft.public.scripting.jscript

このスクリプトが動くんなら、(手元のIE6では動かなかったが) 実行順は「保証できない」と言えるのではないだろうか。(インタプリタの作り方次第でどうにでもなるようなきがするのだがどうだろう)