setTimeout と setInterval
このレッスンで分かること
setTimeout(関数, ミリ秒)は「あとで 1 回だけ呼んでね」という予約ですsetInterval(関数, ミリ秒)は「その間隔でずっと呼んでね」という予約ですclearInterval(番号)で止めるまで、setIntervalは動き続けます
setTimeout と setInterval とは
時間をあつかう 2 つの関数です。あとで動かす予約をして、いまの処理はすぐ次へ進みます。ヘッダーに動く時計を出しながら、非同期の入口をのぞいてみましょう。
JavaScript は 1 本の線で動く
ここまで書いてきたコードは、上から下へ、1 行ずつ順番に動いてきました。ボタンを押したときの処理も同じで、押された瞬間に関数が呼ばれ、その関数が終わってから次の仕事に移ります。JavaScript には作業員が 1 人しかいない、と考えると分かりやすいです。この作業員は、いま手に持っている仕事を最後までやり切るまで、ほかの仕事を始められません。
だからこそ、困ることがあります。たとえば「3 秒待ってからお知らせを消す」をやりたいとき、作業員をその場で 3 秒立ち止まらせてしまうと、その 3 秒のあいだページは完全に固まります。ボタンを押しても反応せず、文字も選べません。待っているあいだ何もできない、というのは Web ページにとって致命的です。
そこで使うのが予約という考え方です。作業員に「3 秒たったら、この紙に書いた仕事をやってね」と伝えて、自分はすぐ次の行へ進みます。3 秒後、手が空いたときに作業員がその紙を拾って実行します。この予約をするのが setTimeout と setInterval です。
予約した仕事は、いま動いている処理がすべて終わってから実行されます。作業員が 1 人なので割り込みはできません。「1000 ミリ秒後ぴったり」ではなく「1000 ミリ秒たった以降で、手が空いたとき」と覚えておくと、あとで時刻がわずかにずれても慌てずにすみます。
setTimeout であとから 1 回だけ動かす
書き方は setTimeout(関数, ミリ秒) です。第 1 引数に「あとでやってほしい処理」を関数のまま渡し、第 2 引数に待つ長さをミリ秒で書きます。
const notice = document.querySelector("#notice");
notice.textContent = "ようこそ。このお知らせは 5 秒で消えます";
setTimeout(function () {
notice.textContent = "";
}, 5000);大事なのは、関数を渡すという点です。setTimeout(showTime(), 1000) と書くと、showTime() がその場で実行され、その戻り値 (多くは undefined) を予約してしまいます。かっこを付けずに setTimeout(showTime, 1000) と書けば、関数そのものを渡せます。第6章のコールバック関数と同じ考え方です。
ミリ秒は 1000 分の 1 秒です。1 秒なら 1000、0.5 秒なら 500、3 秒なら 3000 と書きます。ここを 1 と書いてしまうと 1 ミリ秒後、つまりほぼ一瞬で動いてしまうので気を付けてください。
予約したあとの行はすぐ動く
setTimeout を書いた行で処理が止まるわけではない、というのが最初のつまずきどころです。次のコードを、上から順に読んでみてください。
console.log("1 番目");
setTimeout(function () {
console.log("2 番目のつもり");
}, 1000);
console.log("3 番目のつもり");コンソールに出る順は 1 番目 3 番目のつもり 2 番目のつもり です。予約した処理は、いま動いている流れが終わってから呼ばれるので、下に書いた console.log のほうが先に出ます。書いた順と動く順が一致しない、というのは予約ならではの感覚で、ここが分かると非同期の話がぐっと近くなります。
setInterval でくり返す
setInterval は書き方が setTimeout とそっくりですが、動きが違います。1 回で終わらず、指定した間隔でずっと呼び続けます。時計のように、決まった間隔で表示を新しくしたいときに使います。
const clock = document.querySelector("#clock");
function showTime() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, "0");
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
clock.textContent = hours + ":" + minutes + ":" + seconds;
}
showTime();
const clockTimer = setInterval(showTime, 1000);new Date() はいまの日時を持つオブジェクトです。getHours getMinutes getSeconds で数値を取り出せます。padStart(2, "0") は文字列を 2 文字にそろえるメソッドで、9 時を 09 と表示するために使っています。数値のままでは使えないので String(...) で文字列にしてから呼びます。
setInterval を仕掛ける前に showTime() を 1 回呼んでいるところに注目してください。予約は 1000 ミリ秒後からしか動かないので、これを書かないと最初の 1 秒だけ時計が空っぽになります。細かい話に見えますが、こういう一瞬のちらつきが、できあがりの印象を大きく変えます。
止めないと動き続ける
setInterval の戻り値は、その予約に付けられた番号です。この番号を覚えておかないと、あとで止められません。止めるときは clearInterval(番号) を呼びます。
document.querySelector("#clock-stop").addEventListener("click", function () {
clearInterval(clockTimer);
});止め忘れは実際の現場でよく起きる不具合です。1 秒ごとに動く処理が 10 個たまると、ページはどんどん重くなります。使い終わった予約が残り続けてメモリを食い、動きが鈍っていく状態をメモリリークと呼びます。画面を切り替えたとき、要素を消したとき、役目が終わったときに必ず止める、と決めておくと安全です。
| 関数 | 何をするか | 止め方 |
|---|---|---|
| setTimeout | 指定した時間のあと 1 回だけ呼ぶ | clearTimeout(番号) |
| setInterval | 指定した間隔でくり返し呼ぶ | clearInterval(番号) |
| clearTimeout | setTimeout の予約を取り消す | 取り消す側なので不要 |
| clearInterval | setInterval の予約を止める | 取り消す側なので不要 |
setTimeoutのほうもclearTimeoutで取り消せます。まだ動いていない予約だけが対象で、すでに実行された予約を取り消すことはできません。
非同期の入口はここまで
いま体験したのが非同期という考え方の入口です。すぐ返事がもらえない仕事を予約しておいて、返事が来たときに続きをやる。この形は、通信でデータを取ってくるときにも、ファイルを読むときにも、同じように出てきます。
その本格的な道具が Promise と async と await、そしてサーバーからデータを取ってくる fetch です。ここでは扱いません。入門の目的は「1 本の線で動く JavaScript が、予約という形で時間をあつかえる」と分かるところまでです。続きは実践JavaScriptコースで、通信と組み合わせながらしっかり学びます。いまは setTimeout と setInterval が手になじめば十分です。
よくある間違い
かっこを付けて関数を呼んでしまう間違いが最も多いです。
// 間違い showTime() をその場で実行し、その戻り値を予約している
setInterval(showTime(), 1000);
// 正しい 関数そのものを渡す
setInterval(showTime, 1000);戻り値の番号を受け取らないのも、よくある間違いです。番号が無いと止める手段がありません。
// 間違い あとで止められない
setInterval(showTime, 1000);
// 正しい 番号を変数に入れておく
const clockTimer = setInterval(showTime, 1000);
clearInterval(clockTimer);秒とミリ秒の取り違えも定番です。setTimeout(fn, 3) は 3 秒ではなく 3 ミリ秒後で、ほぼ一瞬です。3 秒待たせたいなら 3000 と書きます。
課題
- ヘッダーに、時刻を出す id が clock の p と、止める id が clock-stop の button と、状態を出す id が clock-status の p と、お知らせ用の id が notice の p を足す
- showTime 関数を作り、clock に時刻を入れる。読み込んだ直後に 1 回呼んでおく
- setInterval で 1 秒ごとに showTime を呼び、戻り値の番号を変数に入れておく
- clock-stop を押したら clearInterval で止め、clock-status に停止中と分かる文を入れる
- setTimeout を 1 つ使い、notice に出したお知らせを 5 秒後に消す