setTimeout と setInterval

このレッスンで分かること

  • setTimeout(関数, ミリ秒) は「あとで 1 回だけ呼んでね」という予約です
  • setInterval(関数, ミリ秒) は「その間隔でずっと呼んでね」という予約です
  • clearInterval(番号) で止めるまで、setInterval は動き続けます

setTimeout と setInterval とは

時間をあつかう 2 つの関数です。あとで動かす予約をして、いまの処理はすぐ次へ進みます。ヘッダーに動く時計を出しながら、非同期の入口をのぞいてみましょう。

JavaScript は 1 本の線で動く

ここまで書いてきたコードは、上から下へ、1 行ずつ順番に動いてきました。ボタンを押したときの処理も同じで、押された瞬間に関数が呼ばれ、その関数が終わってから次の仕事に移ります。JavaScript には作業員が 1 人しかいない、と考えると分かりやすいです。この作業員は、いま手に持っている仕事を最後までやり切るまで、ほかの仕事を始められません。

だからこそ、困ることがあります。たとえば「3 秒待ってからお知らせを消す」をやりたいとき、作業員をその場で 3 秒立ち止まらせてしまうと、その 3 秒のあいだページは完全に固まります。ボタンを押しても反応せず、文字も選べません。待っているあいだ何もできない、というのは Web ページにとって致命的です。

そこで使うのが予約という考え方です。作業員に「3 秒たったら、この紙に書いた仕事をやってね」と伝えて、自分はすぐ次の行へ進みます。3 秒後、手が空いたときに作業員がその紙を拾って実行します。この予約をするのが setTimeoutsetInterval です。

予約した仕事は、いま動いている処理がすべて終わってから実行されます。作業員が 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(番号)
clearTimeoutsetTimeout の予約を取り消す取り消す側なので不要
clearIntervalsetInterval の予約を止める取り消す側なので不要

setTimeout のほうも clearTimeout で取り消せます。まだ動いていない予約だけが対象で、すでに実行された予約を取り消すことはできません。

非同期の入口はここまで

いま体験したのが非同期という考え方の入口です。すぐ返事がもらえない仕事を予約しておいて、返事が来たときに続きをやる。この形は、通信でデータを取ってくるときにも、ファイルを読むときにも、同じように出てきます。

その本格的な道具が Promiseasyncawait、そしてサーバーからデータを取ってくる fetch です。ここでは扱いません。入門の目的は「1 本の線で動く JavaScript が、予約という形で時間をあつかえる」と分かるところまでです。続きは実践JavaScriptコースで、通信と組み合わせながらしっかり学びます。いまは setTimeoutsetInterval が手になじめば十分です。

よくある間違い

かっこを付けて関数を呼んでしまう間違いが最も多いです。

// 間違い showTime() をその場で実行し、その戻り値を予約している setInterval(showTime(), 1000); // 正しい 関数そのものを渡す setInterval(showTime, 1000);

戻り値の番号を受け取らないのも、よくある間違いです。番号が無いと止める手段がありません。

// 間違い あとで止められない setInterval(showTime, 1000); // 正しい 番号を変数に入れておく const clockTimer = setInterval(showTime, 1000); clearInterval(clockTimer);

秒とミリ秒の取り違えも定番です。setTimeout(fn, 3) は 3 秒ではなく 3 ミリ秒後で、ほぼ一瞬です。3 秒待たせたいなら 3000 と書きます。

課題

  1. ヘッダーに、時刻を出す id が clock の p と、止める id が clock-stop の button と、状態を出す id が clock-status の p と、お知らせ用の id が notice の p を足す
  2. showTime 関数を作り、clock に時刻を入れる。読み込んだ直後に 1 回呼んでおく
  3. setInterval で 1 秒ごとに showTime を呼び、戻り値の番号を変数に入れておく
  4. clock-stop を押したら clearInterval で止め、clock-status に停止中と分かる文を入れる
  5. setTimeout を 1 つ使い、notice に出したお知らせを 5 秒後に消す

ヒント

index.html
style.css
script.js
プレビュー

できているか

  • 時刻を出す #clock がある
  • 止めるボタン #clock-stop がある
  • お知らせを出す #notice がある
  • 読み込んだ直後から #clock に時刻が出ている
  • #clock-stop を押すと #clock-status に停止中と出る