関数を今すぐではなく、後の特定の時点で実行することを決定する場合があります。それは「通話のスケジュール設定」と呼ばれます。
それには 2 つの方法があります。
setTimeout
使用すると、一定時間後に関数を 1 回実行できます。
setInterval
すると、一定の時間間隔の後に関数を開始し、その間隔で継続的に繰り返す関数を繰り返し実行できます。
これらのメソッドは JavaScript 仕様の一部ではありません。ただし、ほとんどの環境には内部スケジューラがあり、これらのメソッドが提供されます。特に、これらはすべてのブラウザーと Node.js でサポートされています。
構文:
let timerId = setTimeout(func|code, [遅延], [arg1], [arg2], ...)
パラメータ:
func|code
実行する関数またはコード文字列。通常、それは関数です。歴史的な理由から、コードの文字列を渡すことはできますが、それはお勧めできません。
delay
実行前の遅延 (ミリ秒単位) (1000 ミリ秒 = 1 秒)、デフォルトでは 0。
arg1
、 arg2
…
関数の引数
たとえば、次のコードは 1 秒後にsayHi()
を呼び出します。
関数sayHi() { アラート('こんにちは'); } setTimeout(sayHi, 1000);
引数あり:
関数sayHi(フレーズ、誰) { alert( フレーズ + ', ' + 誰 ); } setTimeout(sayHi, 1000, "こんにちは", "ジョン"); // こんにちは、ジョン
最初の引数が文字列の場合、JavaScript はその文字列から関数を作成します。
したがって、これも機能します:
setTimeout("alert('Hello')", 1000);
ただし、文字列の使用はお勧めできません。代わりに、次のようにアロー関数を使用してください。
setTimeout(() => アラート('Hello'), 1000);
関数を渡しますが、実行はしません
初心者の開発者は、関数の後に括弧()
を追加するという間違いを犯すことがあります。
// 間違っている! setTimeout(sayHi(), 1000);
setTimeout
関数への参照を想定しているため、これは機能しません。ここで、 sayHi()
関数を実行し、その実行結果がsetTimeout
に渡されます。この場合、 sayHi()
の結果はundefined
(関数は何も返さない) なので、何もスケジュールされません。
setTimeout
を呼び出すと、実行をキャンセルするために使用できる「タイマー識別子」 timerId
返されます。
キャンセルする構文は次のとおりです。
let timerId = setTimeout(...); clearTimeout(timerId);
以下のコードでは、関数をスケジュールしてからキャンセルします (気が変わりました)。結果として、何も起こりません。
let timerId = setTimeout(() =>alert(「決して起こらない」), 1000); アラート(タイマーID); // タイマー識別子 clearTimeout(timerId); アラート(タイマーID); // 同じ識別子(キャンセル後もnullにならない)
alert
出力からわかるように、ブラウザではタイマー識別子は数値です。他の環境では、これは別のことになる可能性があります。たとえば、Node.js は追加のメソッドを含むタイマー オブジェクトを返します。
繰り返しますが、これらのメソッドには普遍的な仕様はないので、それは問題ありません。
ブラウザの場合、タイマーについては、HTML Living Standard のタイマー セクションで説明されています。
setInterval
メソッドの構文はsetTimeout
と同じです。
let timerId = setInterval(func|code, [遅延], [arg1], [arg2], ...)
すべての引数は同じ意味を持ちます。ただし、 setTimeout
とは異なり、関数は 1 回だけではなく、指定された時間間隔の後に定期的に実行されます。
それ以上の呼び出しを停止するには、 clearInterval(timerId)
を呼び出す必要があります。
次の例では、2 秒ごとにメッセージが表示されます。 5 秒後に出力が停止します。
// 2秒間隔で繰り返す let timerId = setInterval(() =>alert('tick'), 2000); // 5秒後に停止 setTimeout(() => {clearInterval(timerId);alert('stop'); }, 5000);
alert
が表示されたまま時間が経過する
Chrome や Firefox を含むほとんどのブラウザでは、 alert/confirm/prompt
表示しながら内部タイマーが「カチカチ」鳴り続けます。
したがって、上記のコードを実行し、しばらくalert
ウィンドウを閉じなかった場合、実行するとすぐに次のalert
が表示されます。実際のアラート間の間隔は 2 秒より短くなります。
何かを定期的に実行するには 2 つの方法があります。
1 つはsetInterval
です。もう 1 つは、次のようなネストされたsetTimeout
です。
/** の代わりに: let timerId = setInterval(() =>alert('tick'), 2000); */ let timerId = setTimeout(functiontic() { アラート('ティック'); timerId = setTimeout(tick, 2000); // (*) }、2000);
上記のsetTimeout
、現在の呼び出しの終了直後に次の呼び出しをスケジュールします(*)
。
ネストされたsetTimeout
setInterval
よりも柔軟なメソッドです。このようにして、現在の呼び出しの結果に応じて、次の呼び出しが異なるようにスケジュールされる可能性があります。
たとえば、データを要求するリクエストを 5 秒ごとにサーバーに送信するサービスを作成する必要がありますが、サーバーが過負荷になった場合に備えて、間隔を 10、20、40 秒に増やす必要があります。
疑似コードは次のとおりです。
遅延 = 5000 にします。 let timerId = setTimeout(function request() { ...リクエストを送信... if (サーバーの過負荷によりリクエストが失敗しました) { // 次の実行までの間隔を長くします 遅延 *= 2; } timerId = setTimeout(リクエスト, 遅延); }、 遅れ);
また、スケジュールしている関数が CPU を大量に消費する場合は、実行にかかる時間を測定し、遅かれ早かれ次の呼び出しを計画できます。
ネストされたsetTimeout
を使用すると、 setInterval
よりも正確に実行間の遅延を設定できます。
2 つのコード部分を比較してみましょう。最初のものはsetInterval
を使用します。
i = 1 とします。 setInterval(関数() { 関数(i++); }, 100);
2 番目の例では、ネストされたsetTimeout
を使用します。
i = 1 とします。 setTimeout(関数 run() { 関数(i++); setTimeout(run, 100); }, 100);
setInterval
の場合、内部スケジューラは 100 ミリ秒ごとにfunc(i++)
を実行します。
気づきましたか?
setInterval
のfunc
呼び出し間の実際の遅延は、コード内の遅延よりも短くなります。
func
の実行にかかる時間は間隔の一部を「消費」するため、これは正常です。
func
の実行が予想より長くなり、100 ミリ秒以上かかる可能性があります。
この場合、エンジンはfunc
が完了するのを待ってからスケジューラをチェックし、時間が経過した場合はすぐに再実行します。
エッジケースでは、関数が常にdelay
ミリ秒よりも長く実行される場合、呼び出しはまったく停止せずに行われます。
そして、ネストされたsetTimeout
の図は次のとおりです。
ネストされたsetTimeout
固定遅延 (ここでは 100ms) を保証します。
これは、前の呼び出しの終了後に新しい呼び出しが計画されているためです。
ガベージ コレクションと setInterval/setTimeout コールバック
関数がsetInterval/setTimeout
に渡されると、その関数への内部参照が作成され、スケジューラに保存されます。これにより、関数への参照が他にない場合でも、関数がガベージ コレクションされるのを防ぎます。
// 関数はスケジューラが呼び出すまでメモリ内に留まります setTimeout(function() {...}, 100);
setInterval
の場合、関数は、 clearInterval
が呼び出されるまでメモリ内に留まります。
副作用があります。関数は外部の字句環境を参照するため、関数が存続している間は外部変数も存続します。関数自体よりもはるかに多くのメモリを必要とする場合があります。したがって、スケジュールされた機能が不要になった場合は、たとえそれが非常に小さいものであっても、キャンセルすることをお勧めします。
特別な使用例があります: setTimeout(func, 0)
、または単にsetTimeout(func)
です。
これにより、 func
の実行ができるだけ早くスケジュールされます。ただし、スケジューラは、現在実行中のスクリプトが完了した後にのみ呼び出します。
したがって、関数は現在のスクリプトの「直後」に実行されるようにスケジュールされます。
たとえば、これは「Hello」を出力し、すぐに「World」を出力します。
setTimeout(() => アラート("世界")); アラート("こんにちは");
最初の行は「0ms 後に通話をカレンダーに登録します」。ただし、スケジューラは現在のスクリプトが完了した後にのみ「カレンダーをチェック」するため、 "Hello"
が最初で、 "World"
その後になります。
ゼロ遅延タイムアウトの高度なブラウザ関連の使用例もあります。これについては、「イベント ループ: マイクロタスクとマクロタスク」の章で説明します。
ゼロ遅延は実際にはゼロではありません (ブラウザ上では)
ブラウザでは、ネストされたタイマーを実行できる頻度に制限があります。 HTML Living Standard には、「5 つのネストされたタイマーの後、間隔は少なくとも 4 ミリ秒になるように強制される」と記載されています。
以下の例でそれが何を意味するかを示してみましょう。その中のsetTimeout
呼び出しは、遅延ゼロでそれ自体を再スケジュールします。各呼び出しは、前回の呼び出しからのリアルタイムをtimes
配列に記憶します。実際の遅延はどのようなものですか?見てみましょう:
let start = Date.now(); 回数 = []; setTimeout(関数 run() { 回.push(Date.now() - 開始); // 前回の呼び出しからの遅延を記憶します if (開始 + 100 < Date.now()) アラート(回); // 100ms 後の遅延を表示します それ以外の場合は setTimeout(run); // それ以外の場合は再スケジュール }); // 出力の例: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
最初のタイマーは (仕様に書かれているとおりに) すぐに実行され、その後9, 15, 20, 24...
が表示されます。呼び出し間の必須の 4 ミリ秒以上の遅延が影響します。
setTimeout
代わりにsetInterval
使用すると、同様のことが起こります。 setInterval(f)
遅延ゼロでf
数回実行し、その後は 4 ミリ秒以上の遅延で実行します。
この制限は古代から来ており、多くのスクリプトがそれに依存しているため、歴史的な理由から存在します。
サーバーサイド JavaScript の場合、その制限は存在せず、Node.js の setImmediate など、即時非同期ジョブをスケジュールする他の方法が存在します。したがって、このメモはブラウザー固有のものです。
メソッドsetTimeout(func, delay, ...args)
およびsetInterval(func, delay, ...args)
使用すると、 delay
ミリ秒後にfunc
1 回または定期的に実行できます。
実行をキャンセルするには、 setTimeout/setInterval
によって返された値を使用してclearTimeout/clearInterval
を呼び出す必要があります。
ネストされたsetTimeout
呼び出しは、 setInterval
のより柔軟な代替手段であり、実行間の時間をより正確に設定できます。
setTimeout(func, 0)
によるゼロ遅延スケジューリング ( setTimeout(func)
と同じ) は、「できるだけ早く、ただし現在のスクリプトの完了後」に呼び出しをスケジュールするために使用されます。
ブラウザーは、 setTimeout
の 5 つ以上のネストされた呼び出しまたはsetInterval
(5 回目の呼び出し後) の最小遅延を 4 ミリ秒に制限します。それは歴史的な理由によるものです。
すべてのスケジュール方法で正確な遅延が保証されるわけではないことに注意してください。
たとえば、ブラウザ内のタイマーはさまざまな理由で遅くなる可能性があります。
CPU が過負荷になっています。
ブラウザタブはバックグラウンドモードになっています。
ラップトップはバッテリー節約モードになっています。
これらすべてにより、ブラウザーと OS レベルのパフォーマンス設定に応じて、最小タイマー解像度 (最小遅延) が 300 ミリ秒、さらには 1000 ミリ秒に増加する可能性があります。
重要度: 5
from
から始まりto
で終わる数値を毎秒出力する関数printNumbers(from, to)
を作成します。
ソリューションの 2 つのバリエーションを作成します。
setInterval
を使用します。
ネストされたsetTimeout
を使用します。
setInterval
の使用:
関数 printNumbers(from, to) { 現在 = からとする; let timerId = setInterval(function() { アラート(現在); if (現在 == に) { クリアインターバル(タイマーId); } 現在++; }, 1000); } // 使用法: printNumbers(5, 10);
ネストされたsetTimeout
の使用:
関数 printNumbers(from, to) { 現在 = からとする; setTimeout(関数 go() { アラート(現在); if (現在 < to) { setTimeout(go, 1000); } 現在++; }, 1000); } // 使用法: printNumbers(5, 10);
どちらのソリューションでも、最初の出力の前に初期遅延があることに注意してください。この関数は、初めて1000ms
後に呼び出されます。
関数をすぐに実行したい場合は、次のように別の行に追加の呼び出しを追加できます。
関数 printNumbers(from, to) { 現在 = からとする; 関数 go() { アラート(現在); if (現在 == に) { クリアインターバル(タイマーId); } 現在++; } 行く(); let timerId = setInterval(go, 1000); } printNumbers(5, 10);
重要度: 5
以下のコードでは、 setTimeout
呼び出しがスケジュールされており、その後、重い計算が実行され、完了するまでに 100 ミリ秒以上かかります。
スケジュールされた機能はいつ実行されますか?
ループ後。
ループ前。
ループの最初。
alert
何を表示しますか?
i = 0 とします。 setTimeout(() => アラート(i), 100); //? // この関数の実行時間は 100ms を超えると仮定します for(let j = 0; j < 100000000; j++) { i++; }
setTimeout
は、現在のコードが終了した後にのみ実行されます。
i
最後のものになります: 100000000
。
i = 0 とします。 setTimeout(() => アラート(i), 100); // 100000000 // この関数の実行時間は 100ms を超えると仮定します for(let j = 0; j < 100000000; j++) { i++; }