setTimeout関数やっと分かった!

for文をsetTimeoutに変換する


いやー、最初見たときはさっぱり分かりませんでした。
というのもsetTimeoutが何たるかがいまいち理解できてなかったので。。
input要素のvalue値やstyle値を書き換える程度のjavascriptしか書かない私には高度すぎる内容でした。
けど、順をおって勉強していくと大分分かってきました。

setTimeoutとは

まずはこれを押さえておかないと。
window.setTimeout(function, msec);
これは、第一引数で渡された関数をmsec後に実行します、というもの。基本的にはこれだけ。
以下を実行すると確かに3秒後に「hello」が表示されます。

  setTimeout(function(){ alert('hello'); }, 3000);

しかししかし、正確には実行するんではないみたい。msec後にキュー(関数の待ち行列)に登録するみたい。次の例を見てみると。。

  (function a() {
    console.log('a start.');
    setTimeout(function() { console.log('b'); }, 1000);
    setTimeout(function() { console.log('c'); }, 500);

    // 3[s]くらいの重い処理

    console.log('a end.');
  })();

実行結果は、約3[s]後に以下の結果が表示されます。
a start.
a.end.
c
b
うん、やっぱ実行キューに登録されるだけで、実行するかどうかは関係なさそうだ。FIFOだ。
Javaをやっている人からすると、
a start.
c // 500ms後に表示
b // 1000ms後に表示
a.end. // 3000ms後に表示
という結果を期待してしまうんですけどね。javascriptの処理系はいわゆるマルチスレッドでは無くシングルスレッドってことだ。

再描画タイミング

そもそもなんでfor文をこれで置き換えようと言っているのかが最初よく分からなかった。
理由は再描画のタイミングに関係するよう。
とある関数を実行し、その中で重いforループがあったりするとなかなか画面に結果が反映されず5秒後くらいに「ぱっ」と反映されることがあると思います。
これがあまりにも時間かかりすぎるとfirefoxが警告を出すんだと思います。

こちらに詳しいのですが、
404 Blog Not Found : ページはいつ再描画されるか
再描画タイミングは、関数の実行が終わってから だそうです。

なるほど。だから5秒くらい待った後に「ぱっ」と反映されるんですね。

で、これを解決するのがsetTimeout関数というわけ。ここでやっとつながった!
関数の実行が終わった時に再描画されるから遅いんであれば、毎回関数を終わらせればいいってことだ。

実践

例えばこんな処理があったとします。

  var div = document.getElementById('container_div');

  for(var i=0; i<1000; i++) {
    var input = document.createElement('input');
    input.type='text';
    input.value=i;
    div.appendChild(input);
  }

これ、実際に計ってみると2.3秒くらいかかる。2.3秒後に「ぱっ」と1000個のtextフィールドが表示される。
これをid:amachangさんがいうように一度whileに置き換えてからsetTimeout関数に置き換えてみる。

  var i=0;
  while(true) {
    if(!(i<1000)) break;

    var input = document.createElement('input');
    input.type='text';
    input.value=i;
    div.appendChild(input);
    i++;
  }

で、最後にsetTimeout関数で。

  var i=0;
  setTimeout(function callback(){
    if(!(i<1000)) return;

    var input = document.createElement('input');
    input.type='text';
    input.value=i;
    div.appendChild(input);
    i++;

    setTimeout(callback);
  })

よし、実行!
おぉぉぉお。今度は表示のされ方が変わった!1個1個描画されるようになった!!
・・・けど、おそー。1000個全部表示されるまでに17秒もかかった。
そりゃそうか。毎回再描画が走ってるんだもんな。

というわけで、折衷案

  var i=0;
  setTimeout(function callback(){
    if(!(i<1000)) return;

    for(var j=0; j<200; j++) {
      var input = document.createElement('input');
      input.type='text';
      input.value = i;
      div.appendChild(input);
      i++;
    }

    setTimeout(callback);
  })

こうすれば、再描画が走るタイミングは5回。1回で200個ずつ描画される。
全部表示されるまでの結果は2.5秒。最後に1回まとめて表示するよりはちょっと遅いけど、これで大分改善された♪
総時間は大体同じになったけど、ユーザーの体感速度は違うはず。縦に長い画面なんかの場合は、最後の折衷案を使えば、ユーザーが画面を開いた時にぱっと値が入るので「速くなったねー」と言ってくれるかも。
いやー、勉強になりました。やっと理解出来た★

けど、↓これも難しいんだよな〜(笑)。
http://blog.livedoor.jp/dankogai/archives/50947906.html

プログラム全文

最後の折衷案のプログラム全文を掲載します。ちょっとだけリファクタリングしてたり時間計測のプログラムも入ってたりします。

<html>
<head>
<title>setTimeout勉強</title>

<script type="text/javascript">

function createTextFields() {
  var start = new Date();

  var div = document.getElementById('container_div');

  var counter=0;
  setTimeout(function callback(){
    if(counter>=1000) {
      var end = new Date();
      console.info('hoge4 : ' + (end.getTime() - start.getTime()));
      return;
    }

    for(var i=0; i<200; i++) {
      var input = document.createElement('input');
      input.type='text';
      input.value = counter++;
      div.appendChild(input);
    }

    setTimeout(callback);
  })
}
</script>
</head>
<body>
  <input type="button" id="btn" value="textフィールド生成" onclick="createTextFields();">
  <div id="container_div"></div>
</body>
</html>