概要
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とエラーになる。