概要
JavaScriptのタイマ関連の実装。
- Dateオブジェクトによる経過時間の計測
- setInterval()による等時間間隔の起動
- setTomeout()による等時間間隔の実行キュー登録
Dateオブジェクト
Dateオブジェクトのミリ秒単位の値を用いる方法。
DateオブジェクトのvalueOf()メソッドでミリ秒単位の現在時刻を取得し、これを用いて時間を判定する。これに続く処理を純粋に遅延させることになる。
| 1 2 3 4 5 6 7 | var endTime = ( new Date() ).valueOf() + 3000; console.log( "start" ); while ( ( new Date() ).valueOf() < endTime ); console.log( "end" ); | 
なお、valueOf()メソッドの代わりに、+newオペレータを用いることができる。
| 1 | var endTime = +new Date() + 3000 | 
setInterval()
起動
指定した時間間隔で関数を呼び出す。他の処理とは無関係に呼び出されるため、関数の処理内容が重い場合には、処理が終わる前に呼び出されてしまうことがある。
setInterval()関数の第1引数に繰り返し実行したい関数を、第2引数に繰り返し間隔をミリ秒単位の数値で指定。関数の指定方法としては、以下の3通り。
| 文字列で記述 | 関数名を文字列として記述。その際、関数の後の()も付けて記述 | 
| 直接記述 | 関数名をクォート/ダブルクォートで囲まず直接記述。関数の後ろの()はつけない | 
| 無名関数を使って記述 | function(){}の中にターゲットとなる関数を記述 | 
| 1 2 3 4 5 6 7 8 | # 文字列で関数名を記述する例 setInterval( "animation()"; # 関数名を直接記述する例 setInterval( animation, 1000 ); 1000 ); # 無名関数を使った例 setInterval( function() { animation(); }, 1000 ); | 
引数がある場合
繰り返し実行する関数が引数を持つ場合、文字列で引数を記述するか、無名関数を使って記述する。
| 1 2 3 4 5 6 7 8 | var char0 = '□'; var char1 = '■'; # 文字列で記述する例 setInterval( "animation( char0, char1 )", 1000 ); # 無名関数を使う例 setInterval( function() { animation( char0, char1 ); }, 1000 ); | 
停止
タイマーの起動と停止を制御するには、起動時にsetInterval()を変数で参照し、そのオブジェクトに対してclearInterval()で停止をかける。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <input type="button" id="id_button" value="start/stop" onclick="startAndStop()"> <p id="id_target"></p> <script> <!-- var target = document.getElementById( "id_target" ); var char0 = '□'; var char1 = '■'; var isRunning = false; var flag = 0; var timer; function startAndStop() {   if ( isRunning ) {     clearInterval( timer );     isRunning = false;   } else {     timer = setInterval( "animation( char0, char1 )", 1000 );     isRunning = true;   } } function animation( ch0, ch1 ) {   if ( flag == 0 ) {     target.innerHTML = ch0;     flag = 1;   } else {     target.innerHTML = ch1;     flag = 0;   } } //--> </script> | 
setTimeout()
基本形
setTimeout()の第1引数に実行したい関数を文字列で、第2引数に遅延時間をミリ秒で指定する。
| 1 2 3 4 5 6 7 8 9 | console.log( "start" ); setTimeout( "main()", 3000 ); console.log( "end" ); function main() {   console.log( "in main()" ); } | 
上記の例では、以下のように推移する。
- “start”を表示
- setTimeout()を実行→3秒後に- main()関数が呼び出されるよう指定
- “end”を表示
- 約3秒後にmain()が実行され、”in main()”を表示
起動
関数を文字列とする方法
| 1 2 3 | setTimeout( "main()", 3000 ); setTimeout( "main( 100 )", 3000 ); setTimeout( "main( arg )", 3000 ); | 
文字列で引数を分離する方法
| 1 2 | setTimeout( "main(" + 100 + ")", 3000); setTimeout( "main(" + arg + ")", 3000 ); | 
無名関数を使う方法
| 1 2 3 | setTimeout( function() { main(); }, 3000 ); setTimeout( function() { main( 100 ); }, 3000 ); setTimeout( function() { main( arg ); }, 3000 ); | 
タイマとしての利用
等時間間隔で実行したい関数で、setTimeout()において再帰的に自己を呼び出すことによってタイマーとして利用できる。
| 1 2 3 4 5 6 7 8 9 10 11 12 | counter( 10 ); function counter( n ) {   if ( n == 0 ) {     console.log( "count end" );   } else {     console.log( n );     n--;     timer = setTimeout( function() { counter( n ); }, 1000 );   } } | 
遅延時間後に実行キューに登録されることから、それぞれの関数の実行は重複することなく担保される。このため、setInterval()に比べて関数間の衝突は生じないが、実際の実行間隔は遅延時間より長くなる。
停止
setTimeout()はsetInterval()と同じくタイマーIDを返し、これに対してclearTimeout()を実行することでタイマーを停止できる。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var timer; setTimeout( function() { stop(); }, 5000 ); counter( 10 ); function counter( n ) {   if ( n == 0 ) {     console.log( "count end" );   } else {     console.log( n );     n--;     timer = setTimeout( function() { counter( n ); }, 1000 );   } } function stop() {   clearTimeout( timer ); } | 
上記の例では、タイマーを止めるstop()関数が5秒後に実行されるようにし、counter()を開始している。counter()自身がが終了する前にstop()が実行され、counter()は停止する。
補足
実行キュー
setTimeout()は、指定した関数を指定した遅延時間後に実行キューに登録する。このため、setTimeout()実行後に時間がかかる処理に移った場合、その処理中に遅延指定した関数がキューに登録され、処理実行後に関数が実行される。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var endTime = ( new Date() ).valueOf() + 5000; console.log( "start" ); setTimeout( function() { hoo(); }, 3000 ); while ( ( new Date() ).valueOf() < endTime ); console.log( "end" ); function hoo() {   console.log( "delay" ); } | 
上記の例の場合、
- setTimeout()が実行され、3秒の遅延時間のカウント開始
- whileループに入る
- 3秒後、hoo()が実行キューに登録される
- 5秒後、whileループから抜ける
- 実行キューに登録されたhoo()が即時実行される
スタック・オーバーフローの回避
setTimeout()を用いるシーンの一つに、再帰定義のコールスタック・オーバーフローの回避がある。この場合、再帰呼び出しの度にsetTimeout()でラップし、待機時間をゼロとする。
| 1 | setTimeout( 関数, 0 ); | 
これにより、関数はスタックではなく実行キューに並べられ、オーバーフローは発生しない。ただし、通常の再帰呼び出しに比べて大幅に実行時間が増加する。
たとえば以下の例では単純なループにかかる時間を測っており、
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function countdown( n ) {   if ( n == 0 ) {     console.log( "end" );   } else {     n--;     countdown( n );   } } console.log( "start" ); console.time( "timer" ); countdown( 10000 ); console.timeEnd( "timer" ); // 実行結果 // start // end //timer: 10.000ms | 
次の例では、再帰的にsetTimeout()で自己を呼び出すようにしているが、処理時間の乖離が大きい。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function countdown( n ) {   if ( n == 0 ) {     console.log( "end" );     console.timeEnd( "timer" );   } else {     n--;     setTimeout( function() {       countdown( n );      }, 0 );   } } console.log( "start" ); console.time( "timer" ); countdown( 10000 ); // 実行結果 // start // end // timer: 40317.000ms | 
なお、この方法で関数をsetTimeout()に引き渡す際、引数まで含めて文字列で引き渡そうとするとUncaught ReferenceError: 引数 is not definedとエラーになる。