JavaScript で「数秒後に処理を実行したい」「一定間隔で同じ処理を繰り返したい」というとき、出番になるのが setTimeout() と setInterval() です。この記事では、一定時間後に1回だけ実行する setTimeout() と、一定の間隔で繰り返し実行する setInterval() の使い方を、構文・引数・戻り値のタイマーID、そして clearTimeout() / clearInterval() で止める方法まで順に解説します。数秒後に通知を消す、カウントダウンや時計を動かすといった実践例に加え、遅延時間が正確でない点やループ内で使うときの落とし穴など、初心者がつまずきやすいポイントも具体的に取り上げます。
目次
setTimeout と setInterval でできること
JavaScript はコードを上から順に実行しますが、setTimeout() と setInterval() を使うと「時間に関する処理」を組み込めます。どちらもブラウザ(や Node.js)が提供するタイマー機能で、指定した時間に合わせてコールバック関数を呼び出してくれます。
違いはシンプルで、setTimeout() は「指定した時間が経ったら1回だけ実行」、setInterval() は「指定した間隔ごとに繰り返し実行」します。たとえば数秒後にメッセージを消すなら setTimeout()、1秒ごとに時計を更新し続けるなら setInterval()、といった使い分けになります。
| 関数 | 動作 |
|---|---|
setTimeout() | 指定した時間が経過した後、コールバックを1回だけ実行する |
setInterval() | 指定した間隔ごとに、コールバックを繰り返し実行する |
基本の使い方と構文
まずは setTimeout() の最小の例です。第1引数に実行したい関数、第2引数に待つ時間(ミリ秒)を渡します。1000 ミリ秒=1秒なので、次のコードは約2秒後にメッセージを表示します。
// 2000 ミリ秒(2秒)後にコールバックを1回だけ実行する
setTimeout(() => {
console.log("2秒経ちました");
}, 2000);
setInterval() も書き方はほとんど同じで、第1引数にコールバック、第2引数に間隔(ミリ秒)を渡します。次のコードは1秒ごとにメッセージを表示し続けます。後で説明する方法で止めない限り、ずっと繰り返される点に注意してください。
// 1000 ミリ秒(1秒)ごとにコールバックを繰り返し実行する
setInterval(() => {
console.log("1秒ごとに呼ばれます");
}, 1000);
引数の意味
どちらの関数も引数の構成は共通です。第3引数以降に値を渡すと、それがそのままコールバックの引数として渡されます。各引数の意味を次の表にまとめました。
| 引数 | 説明 |
|---|---|
| 第1引数(コールバック関数) | 時間が来たときに実行する関数。必須 |
| 第2引数(遅延・間隔ミリ秒) | 待つ時間(setTimeout)または繰り返す間隔(setInterval)をミリ秒で指定。省略すると 0 とみなされる |
| 第3引数以降(追加の引数) | コールバックに渡したい値。指定した順にコールバックの引数として渡される |
第3引数以降を使うと、コールバックに値を渡せます。たとえば次のように書くと、name と count がそのまま関数の引数として渡ります。
function greet(name, count) {
console.log(`${name} さん、${count} 回目の呼び出しです`);
}
// 第3引数以降が greet の引数になる("田中", 1 が渡る)
setTimeout(greet, 1000, "田中", 1);
戻り値はタイマーID
setTimeout() と setInterval() は、呼び出すと「タイマーID」という数値を返します。これは登録したタイマーを識別するための番号で、後からそのタイマーを止めるときに使います。止める必要があるなら、戻り値を必ず変数に保存しておきましょう。
// 戻り値(タイマーID)を変数に保存しておく
const timerId = setInterval(() => {
console.log("実行中");
}, 1000);
console.log(timerId); // タイマーIDを表す数値が表示される
clearTimeout と clearInterval で止める
登録したタイマーは、対応する解除関数にタイマーIDを渡すことで止められます。setTimeout() で登録したものは clearTimeout()、setInterval() で登録したものは clearInterval() で解除します。
| 解除関数 | 対象 | 引数 |
|---|---|---|
clearTimeout(id) | setTimeout() で登録したタイマー | 解除したいタイマーID |
clearInterval(id) | setInterval() で登録したタイマー | 解除したいタイマーID |
setTimeout() はまだ実行されていない(待機中の)タイマーであれば、clearTimeout() でキャンセルできます。次の例では、2秒後に実行する予定だった処理を、その前に取り消しています。
const timerId = setTimeout(() => {
console.log("この処理は実行されない");
}, 2000);
// 実行される前に取り消す
clearTimeout(timerId);
setInterval() は止めない限り繰り返し続けるので、clearInterval() で明示的に停止することが特に重要です。次の例は、1秒ごとにカウントを増やし、5回に達したら自分自身を止めます。コールバックの中から clearInterval() を呼べる点を押さえておきましょう。
let count = 0;
const timerId = setInterval(() => {
count += 1;
console.log(`${count} 回目`);
// 5回繰り返したら停止する
if (count >= 5) {
clearInterval(timerId);
console.log("停止しました");
}
}, 1000);
数秒後に通知メッセージを消す
ここからは実践的な例を見ていきます。まずは setTimeout() の定番、「表示した通知を数秒後に自動で消す」処理です。要素を表示してから setTimeout() で数秒待ち、時間が来たら非表示にする、という流れです。
const toast = document.querySelector("#toast");
function showToast(message) {
toast.textContent = message;
toast.style.display = "block";
// 3秒後に自動で非表示にする
setTimeout(() => {
toast.style.display = "none";
}, 3000);
}
showToast("保存しました");
setInterval でカウントダウンを動かす
次は setInterval() を使ったカウントダウンです。1秒ごとに数値を1ずつ減らし、画面の表示を更新します。0になったら clearInterval() でタイマー自身を止めるのがポイントです。下のデモはスタート・ストップ・リセットができるようにしてあります。タブを切り替えてコードを確認し、ボタンを押して動きを試してみてください。
<div class="demo">
<p class="display" id="display">10</p>
<div class="buttons">
<button id="start" type="button">スタート</button>
<button id="stop" type="button">ストップ</button>
<button id="reset" type="button">リセット</button>
</div>
</div>
body {
margin: 0;
font-family: sans-serif;
color: #333;
}
.demo {
padding: 24px;
text-align: center;
}
.display {
font-size: 64px;
font-weight: bold;
margin: 0 0 16px;
color: #4f46e5;
}
.buttons {
display: flex;
gap: 8px;
justify-content: center;
}
button {
padding: 8px 16px;
border: none;
border-radius: 6px;
background: #4f46e5;
color: #fff;
font-weight: bold;
cursor: pointer;
}
button:disabled {
background: #b4b0e8;
cursor: not-allowed;
}
const display = document.querySelector("#display");
const startBtn = document.querySelector("#start");
const stopBtn = document.querySelector("#stop");
const resetBtn = document.querySelector("#reset");
let count = 10;
let timerId = null; // タイマーIDの保管場所(停止に使う)
function start() {
// すでに動いていれば二重起動を防ぐ
if (timerId !== null) return;
startBtn.disabled = true;
timerId = setInterval(() => {
count -= 1;
display.textContent = count;
// 0 になったら自分自身を止める
if (count <= 0) {
clearInterval(timerId);
timerId = null;
startBtn.disabled = false;
}
}, 1000);
}
function stop() {
clearInterval(timerId);
timerId = null;
startBtn.disabled = false;
}
function reset() {
stop();
count = 10;
display.textContent = count;
}
startBtn.addEventListener("click", start);
stopBtn.addEventListener("click", stop);
resetBtn.addEventListener("click", reset);
同じ仕組みで時計も作れます。setInterval() の中で new Date() から現在時刻を取り出し、1秒ごとに表示を書き換えれば、リアルタイムに動く時計になります。
const clock = document.querySelector("#clock");
function update() {
// 現在時刻を「時:分:秒」の形式で表示する
clock.textContent = new Date().toLocaleTimeString();
}
update(); // 最初の1回はすぐ表示する
setInterval(update, 1000); // 以降は1秒ごとに更新する
遅延時間は「最低限の待ち時間」でしかない
タイマーを使ううえで最初に知っておきたいのが、指定した時間はあくまで「最低でもこれだけ待つ」という目安だという点です。タイマーの仕組みを正しく理解しておくと、思わぬ挙動に戸惑わずに済みます。
指定時間ぴったりには実行されない
JavaScript は基本的に1つの処理を順番にこなしていくため、指定時間が来てもメインの処理が動いている間はコールバックを実行できません。タイマーは「指定時間が経ったら実行待ちの列に並ぶ」だけで、実際に呼ばれるのは手が空いたタイミングです。そのため setTimeout(fn, 1000) と書いても、ちょうど1000ミリ秒後に呼ばれるとは限らず、わずかに遅れることがあります。「正確な時刻に合わせたい」という用途には向かないと覚えておきましょう。
setInterval は処理が重いと間隔が詰まる
同じ理由で、setInterval() もコールバックの処理が間隔より長くかかると、次の実行が遅れたり、待機していた呼び出しが連続して走ったりすることがあります。重い処理を短い間隔で繰り返すと、こうした「詰まり」が起きやすくなります。
これを避けたいときは、setInterval() の代わりに setTimeout() を再帰的に呼び出す方法がよく使われます。1回の処理が終わってから次のタイマーをセットするので、処理が重なりにくく、間隔を安定させやすくなります。
function tick() {
// ここで何らかの処理を行う
console.log("処理を実行");
// 処理が終わってから次のタイマーをセットする
setTimeout(tick, 1000);
}
// 最初の1回を起動する
setTimeout(tick, 1000);
ループの中で setTimeout を使うときの注意
「1秒おきに順番に処理したい」と考えて、for ループの中で setTimeout() を使うと、変数のスコープによっては意図しない結果になります。これは var と let の違いに関わる、有名なつまずきポイントです。
var で宣言したカウンタ変数はループ全体で1つだけ共有されます。setTimeout() のコールバックが実際に動くのはループが終わった後なので、その時点の i の値(最終値)を全部のコールバックが参照してしまいます。
// var は1つの変数を共有してしまう
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0,1,2 ではなく「3」が3回表示される
}, i * 1000);
}
解決策はシンプルで、var を let に変えるだけです。let はループの繰り返しごとに新しい変数が作られるため、各コールバックがその時点の値を覚えていてくれます(これがクロージャの働きです)。特別な理由がなければ let を使いましょう。
// let なら繰り返しごとに別々の変数になる
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0,1,2 と順に表示される
}, i * 1000);
}
タイマーの二重起動を防ぐ
ボタンを押すたびに setInterval() を呼ぶような作りにしていると、押すごとに新しいタイマーが増えていき、同じ処理が何重にも走ってしまうことがあります。たとえばカウントダウンならスタートボタンを2回押すと2倍の速さで進む、といった不具合です。
これを防ぐには、タイマーIDを変数で管理し、すでに動いているなら新しく登録しない、あるいは新しく始める前に必ず clearInterval() で止める、という対策が有効です。先ほどのカウントダウンのデモでも、timerId が null でないときは start() を途中で抜けることで二重起動を防いでいました。止めるときは clearInterval() を呼んだうえで、変数を null に戻して「今は動いていない」状態を明示しておくと管理しやすくなります。
まとめ
setTimeout() は指定時間後に1回だけ、setInterval() は一定間隔で繰り返しコールバックを実行する関数です。どちらも第1引数にコールバック、第2引数にミリ秒を渡し、第3引数以降でコールバックへ値を渡せます。戻り値のタイマーIDを保存しておけば、clearTimeout() / clearInterval() で後から止められます。通知の自動非表示には setTimeout()、カウントダウンや時計の更新には setInterval() が向いています。
使ううえでは、指定時間はあくまで最低限の待ち時間で正確ではないこと、setInterval() は重い処理で詰まりやすく setTimeout() の再帰呼び出しで代替できること、ループ内では let を使ってクロージャの罠を避けること、そして必ず clear 系で止めて二重起動を防ぐこと、を押さえておきましょう。これらを意識すれば、時間に関する処理を安定して扱えるようになります。