Code Tips‎ > ‎

JavaScriptのsetTimeoutとsetInterval



JavaScriptのsetTimeoutとsetIntervalの話です。

setTimeoutは、 指定された処理を指定された時間後に1回実行します。
setIntervalは、指定された処理を指定された間隔で繰り返し実行します。

動作は異なりますが、引数の指定の仕方は同じです。setTimeout, setIntervalともに、第一引数にはコード文字列か関数オブジェクトを指定します。第二引数には処理待ち時間もしくは実行間隔をmsecで指定します。

文字列による処理の指定

第一引数にコード文字列を指定した場合、setTimeout(もしくはsetInterval、以下省略)関数の呼び出し時点で文字列を構文解析し実行可能か判断した上で実行します。

ですので、もしもコード文字列に構文エラーがあっても、setTimeout関数の呼び出しに達するまで構文にエラーがあるかどうかわかりません(通常は実行前の読み込み時点でエラーが出る)

また、関数呼び出し時に構文チェックを行う分、処理がわずかに遅れます(多くの場合無視してもかまわないようなわずかな遅延ですが)。ですので、基本的には関数オブジェクトで指定すべきです。特に関数を一つ呼ぶだけの処理ならば、必ず関数オブジェクトの指定にすべきです。

すでに関数を用意していて
setTimeout("myFunc()", 200);
のように指定することは全くの無駄です。

必ずこう書くべきです。
setTimeout(myFunc, 200);

ただし、関数にするほどでもないコードのみであり、かつわずかに処理遅延が発生することは理解した上で、それでもかまわないと判断した場合には、コード文字列での指定でもいいと思います。

setTimeoutでコールバック関数に引数を渡したいとき

ループを回し、0.5秒ごとにインデックスを渡し処理させたい場合を考えます。

for (var i = 0; i < 5; i++) {
	var code = "console.log(i)";
	setTimeout(code, 500 * i);
}

こう書くと、「console.log(i)>」というコードが0.5秒ごとに実行されます。しかしループは一瞬で終了しiが5に達した後での実行になるので、5が5回表示されることになります。

それではと、以下のように書くとコード文字列を作成する時点のiが組み込まれるので、期待通りの動きをします。
for (var i = 0; i < 5; i++) {
	var code = "console.log(" + i + ")";
	setTimeout(code, 500 * i);
}
この例の場合はこれでもいいと思います。

しかし単純に文字列結合をしてコード文字列を作成しても問題がない場合に限られます。引数に配列やオブジェクト型を指定するの場合、この方法を単純に適用することはできません。

JSON.stringifyにかけて文字列にして文字列を引数に指定し、関数側でJSON.parseすれば可能ですが、非常に冗長なつくりになるためお勧めできません。

このような場合にはクロージャーを使用します。
for (var i = 0; i < 5; i++) {
	setTimeout(
		(function(local) { // (1)
			return function() { // (3)
				console.log(local);
			}
		})(i), // (2)
		500 * i
	);
}

こうすることによって、外側の無名関数(1)が実行されたときに、引数(2)で指定されたiを保持するクロージャー(3)が作成され、クロージャーがsetTimeoutの引数に渡されます。

クロージャーの使い方の好例だと思います(なぜ無名関数を2つ書くのか等、このコードの意味が分からない場合には、変数のスコープやクロージャーについての知識・理解が不足しているものと思われます。そのあたりはこのページでは説明しませんが、ぜひ勉強してください)

外側の無名関数の中にsetTimeoutを入れてしまえばもう少しシンプルに書けます。
for (var i = 0; i < 5; i++) {
	(function(local) {
		setTimeout(function() {console.log(local);}, 500 * i);
	})(i);
}


と、ここまで書きましたが、実は多くのメジャーブラウザではクロージャーを使わなくとも、第三引数に指定するだけで非常にシンプルに実装できます。
for (var i = 0; i < 5; i++) {
	setTimeout(function(i) {console.log(i)}, 500 * i, i);
}

ただしIEでは9以前では、使用できません。また、スマホではいろいろなブラウザが登場しておりそれらの対応状況はわかりません。より多くのブラウザに対応させたい場合には、まだ選択しにくい方法だと思います。

2016/08/11