MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
JavaScriptの基礎 - タイマのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
JavaScriptの基礎 - タイマ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == JavaScriptには、処理の実行タイミングを制御するタイマAPIが複数存在する。<br> 主要なタイマ機構として、<code>setTimeout</code>、<code>setInterval</code>、<code>requestAnimationFrame</code> の3つがある。<br> <br> <code>setTimeout</code> は指定した遅延時間後に処理を1回実行するタイマである。<br> <code>setInterval</code> は指定した間隔で処理を繰り返し実行するタイマである。<br> <code>requestAnimationFrame</code> はブラウザの描画タイミングに同期してアニメーションを実行するためのAPIである。<br> <br> これらのタイマはJavaScriptのイベントループと密接に関連している。<br> <code>setTimeout</code> と <code>setInterval</code> はマクロタスクキューに投入され、現在の実行コンテキストとマイクロタスク (Promise等) が完了した後に実行される。<br> <code>requestAnimationFrame</code> はWebブラウザの再描画前に実行されるため、スムーズなアニメーション実装に適している。<br> <br> また、頻繁に発生するイベントの処理最適化手法として、デバウンスとスロットルがある。<br> これらはタイマAPIを活用した応用パターンである。<br> <br> Reactにおいては、コンポーネントのライフサイクルと連動させるために <code>useEffect</code> 内でタイマを管理し、クリーンアップ関数でタイマを解除することが重要である。<br> <br><br> == setTimeout / clearTimeout == <code>setTimeout</code> は、指定した遅延時間 (ミリ秒) の経過後に、コールバック関数を1回だけ実行するタイマ関数である。<br> 実行をキャンセルするには <code>clearTimeout</code> を使用する。<br> <br> ==== 基本的な使い方 ==== 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const timeoutID = setTimeout(callback, delay, arg1, arg2, ...); clearTimeout(timeoutID); </syntaxhighlight> <br> 引数の詳細を以下に示す。<br> <br> <center> {| class="wikitable" |+ 引数の説明 ! 引数 !! 説明 |- | callback || 遅延後に実行するコールバック関数 |- | rowspan="2" | delay | 遅延時間 (ミリ秒)<br>省略時は最速で実行される。 |- | Webブラウザの最小遅延時間は約4[ms] (ネストが深い場合) |- | arg1, arg2, ... || コールバック関数に渡す追加の引数 (省略可能) |} </center> <br> コールバックへ引数を渡す使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> setTimeout((name, age) => { console.log(`Hello ${name}, you are ${age} years old`); }, 1000, "Alice", 30); </syntaxhighlight> <br> ==== 戻り値 (タイマID) とクリア ==== <code>setTimeout</code> は正の整数値であるタイマID (<code>timeoutID</code>) を返す。<br> このIDは同一グローバルスコープ内で一意に識別され、<code>clearTimeout</code> によるキャンセルに使用する。<br> <br> タイマのキャンセル例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> let timerId = setTimeout(() => { console.log("This will run after 2 seconds"); }, 2000); // タイマをキャンセル clearTimeout(timerId); </syntaxhighlight> <br> タイマIDに関する仕様を以下に示す。<br> * <code>setTimeout</code> と <code>setInterval</code> はID共有プール内でIDを生成するため、互いに異なるIDが割り当てられる * <code>clearTimeout</code> に存在しないIDを渡した場合は何もしない (エラーにならない) * タイマIDは変数に保存しておくことで、後からキャンセルできる <br> ==== 0ms指定とイベントループ ==== <code>setTimeout(callback, 0)</code> を指定しても、コールバックは即座には実行されない。<br> これはJavaScriptのイベントループの仕組みによるものである。<br> <br> イベントループの処理順序を以下に示す。<br> # コールスタック内のコードを実行する。 # マイクロタスクキューを全て処理する。(<code>Promise.then()</code>、<code>queueMicrotask()</code> 等) # 必要な場合、DOMを再描画する。 # マクロタスクキューから1つのタスクを実行する。(<code>setTimeout</code>、<code>setInterval</code>、I/O 等) <br> <code>setTimeout</code> はマクロタスクキューに投入されるため、現在のスクリプトと全てのマイクロタスクが完了してから実行される。<br> 実行順序の確認例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> console.log('1. Sync'); setTimeout(() => { console.log('5. Macro task (setTimeout)'); }, 0); Promise.resolve() .then(() => { console.log('3. Micro task (Promise)'); }); queueMicrotask(() => { console.log('4. Micro task (queueMicrotask)'); }); console.log('2. Sync'); // 出力: // 1. Sync // 2. Sync // 3. Micro task (Promise) // 4. Micro task (queueMicrotask) // 5. Macro task (setTimeout) </syntaxhighlight> <br> この特性を利用することで、重い処理をメインスレッドをブロックせずに分割して実行できる。<br> 応用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> async function processLargeDataset(data) { const chunkSize = 100; for (let i = 0; i < data.length; i += chunkSize) { const chunk = data.slice(i, i + chunkSize); processChunk(chunk); // マクロタスクに制御を返すことで、UIの描画をブロックしない await new Promise(resolve => setTimeout(resolve, 0)); } } </syntaxhighlight> <br><br> == setInterval / clearInterval == <code>setInterval</code> は、指定した間隔 (ミリ秒) ごとにコールバック関数を繰り返し実行するタイマ関数である。<br> 繰り返し実行を停止するには、<code>clearInterval</code> を使用する。<br> <br> ==== 基本的な使い方 ==== 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const intervalID = setInterval(callback, delay, arg1, arg2, ...); clearInterval(intervalID); </syntaxhighlight> <br> 引数の仕様は、<code>setTimeout</code> と同じである。<br> 戻り値は、<code>intervalID</code> で、<code>clearInterval</code> で繰り返し実行をキャンセルする。<br> <br> 使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> let count = 0; const intervalID = setInterval(() => { console.log(`Count: ${count++}`); }, 1000); // 5秒後にタイマを停止 setTimeout(() => clearInterval(intervalID), 5000); </syntaxhighlight> <br> ==== 注意点 (ドリフト、重複実行) ==== <code>setInterval</code> には以下に示すような問題点がある。<br> <br> <center> {| class="wikitable" |+ setInterval の問題点 ! 問題点 !! 説明 |- | rowspan="2" | タイマドリフト | メインスレッドがブロックされた場合、実行タイミングがずれる。 |- | コールバックの処理時間がインターバルより長い場合、実行タイミングが遅延する。 |- | rowspan="2" | 重複実行 | 前のコールバックの実行が完了する前に、次の実行がスケジュールされる可能性がある。 |- | 非同期処理を含む場合に複数のコールバックが並行実行される危険がある。 |- | メモリリーク || <code>clearInterval</code> を忘れると、バックグラウンドで永続的に実行され続ける。 |- | 不柔軟な間隔 || コールバックの実行時間を考慮せず、固定間隔でスケジュールする。 |} </center> <br> 重複実行が問題になるケースを以下に示す。<br> <br> <syntaxhighlight lang="javascript"> // 問題のあるパターン : fetchDataが3秒掛かる場合 setInterval(async () => { const data = await fetchData(); // 3秒掛かる console.log(data); }, 2000); // 2秒ごとに呼び出すため、複数が並行実行される </syntaxhighlight> <br> ==== setTimeoutによる代替パターン ==== <code>setInterval</code> の問題を回避するために、再帰的な <code>setTimeout</code> を使用するパターンが推奨される。<br> このパターンでは、前の実行が完了した後に次の実行をスケジュールするため、重複実行が発生しない。<br> <br> * 代替パターン1 : 再帰的 <code>setTimeout</code> を以下に示す。 *: <syntaxhighlight lang="javascript"> async function repeatTask() { try { const data = await fetchData(); console.log(data); } catch (error) { console.error(error); } // 前の実行が完了した後に次の実行をスケジュール setTimeout(repeatTask, 2000); } repeatTask(); </syntaxhighlight> *: <br> * 代替パターン2 : キャンセル可能な <code>async/await</code> ループを以下に示す。 *: <syntaxhighlight lang="javascript"> class CancellableTask { constructor() { this.cancelled = false; } async start() { while (!this.cancelled) { try { await fetchData(); } catch (error) { console.error(error); } await new Promise(resolve => setTimeout(resolve, 2000)); } } cancel() { this.cancelled = true; } } </syntaxhighlight> <br><br> == requestAnimationFrame == <code>requestAnimationFrame</code> は、Webブラウザの再描画タイミングに同期してコールバックを実行するAPIである。<br> スムーズなアニメーションの実装に使用され、<code>setTimeout</code> や <code>setInterval</code> よりもアニメーションに適している。<br> <br> ==== 基本的な使い方 ==== 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const requestID = requestAnimationFrame(callback); cancelAnimationFrame(requestID); </syntaxhighlight> <br> * <code>requestAnimationFrame(callback)</code> はコールバック関数を登録して、<code>requestID</code> を返す。 * コールバックは次の再描画前に1回だけ実行される。(ワンショット) * <code>cancelAnimationFrame(requestID)</code> で登録をキャンセルする。 * アニメーションループを継続するには、コールバック内で再度 <code>requestAnimationFrame</code> を呼び出す。 <br> ==== 特性 (リフレッシュレート同期、非アクティブタブでの一時停止) ==== <code>requestAnimationFrame</code> の特性を以下に示す。<br> <br> ===== リフレッシュレートとの同期 ===== Webブラウザはディスプレイのリフレッシュレートに合わせてコールバックを呼び出す。<br> <br> <center> {| class="wikitable" |+ リフレッシュレートと実行間隔 ! リフレッシュレート !! 実行間隔 (近似値) |- | 60[Hz] || 約16.67[ms]ごと |- | 120[Hz] || 約8.33[ms]ごと |- | 144[Hz] || 約6.94[ms]ごと |} </center> <br> ===== 非アクティブタブでの一時停止 ===== タブがバックグラウンドに移動した場合、<code>requestAnimationFrame</code> は自動的に一時停止する。<br> この特性により、以下のメリットがある。<br> <br> * CPU使用率の削減 * バッテリー消費の低減 * 不要な描画処理の抑制 <br> 対比として、<code>setTimeout</code> と <code>setInterval</code> はバックグラウンドタブでも実行され続ける。<br> (Webブラウザによっては1秒以上の遅延が加えられる場合がある)<br> <br> ===== timestampパラメータ ===== コールバック関数はWebブラウザ起動からの経過時間 (ミリ秒) を <code>timestamp</code> パラメータとして受け取る。<br> このパラメータを使用することにより、フレームレートに依存しない時間ベースのアニメーションを定義できる。<br> <br> ==== アニメーションループの実装 ==== <code>requestAnimationFrame</code> を使用した時間ベースのアニメーションループの実装例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> let startTime = null; const duration = 3000; // アニメーション時間: 3秒 function animateWithDuration(currentTime) { if (startTime === null) startTime = currentTime; const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // progressに基づいて要素のスタイルを変更 (0 -> 1) element.style.opacity = progress; // アニメーションが完了していなければ次のフレームを要求 if (progress < 1) { requestAnimationFrame(animateWithDuration); } } requestAnimationFrame(animateWithDuration); </syntaxhighlight> <br><br> == デバウンスとスロットル == デバウンスとスロットルは、頻繁に発生するイベントの処理を最適化するためのパターンである。<br> どちらも <code>setTimeout</code> を活用して実装される。<br> <br> ==== デバウンス ==== デバウンスは、イベントが連続で発生する場合に、最後のイベントから指定時間経過後に初めて処理を実行するパターンである。<br> <br> ユーザの操作が完了したことを確認してから処理を実行する場合に有効である。<br> <br> 主な使用例を以下に示す。<br> <br> <center> {| class="wikitable" |+ 主な使用例 ! 使用例 !! 説明 |- | 検索入力 || ユーザが入力を止めた後にAPIを呼び出す。 |- | フォーム検証 || 入力完了後に検証を実行する。 |- | ウィンドウリサイズ || リサイズ完了後にレイアウトを更新する。 |} </center> <br> デバウンスの実装例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function debounce(fn, delay) { let timeoutId = null; return function(...args) { // 前のタイマをキャンセル if (timeoutId) clearTimeout(timeoutId); // 新しいタイマを設定 timeoutId = setTimeout(() => { fn.apply(this, args); timeoutId = null; }, delay); }; } const handleSearch = debounce(async (e) => { const query = e.target.value; // APIを呼び出す (入力停止から500[ms]後に実行) }, 500); searchInput.addEventListener('input', handleSearch); </syntaxhighlight> <br> ==== スロットル ==== スロットルは、一定時間ごとに最大1回だけ処理を実行するパターンである。<br> 定期的な監視が必要 かつ 処理頻度を制限したい場合に有効である。<br> <br> 下表に、主な使用例を示す。<br> <br> <center> {| class="wikitable" |+ 主な使用例 ! 使用例 !! 説明 |- | スクロールイベント || スクロール位置の監視 |- | マウス移動 || マウス座標の追跡 |- | ゲームのキー入力 || 一定間隔でのアクション実行 |} </center> <br> スロットルの実装例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function throttle(fn, interval) { let lastTime = 0; return function(...args) { const now = Date.now(); // 前回の実行から指定間隔が経過した場合のみ実行 if (now - lastTime >= interval) { lastTime = now; fn.apply(this, args); } }; } const handleScroll = throttle(() => { console.log(`Scroll: ${window.scrollY}`); }, 200); window.addEventListener('scroll', handleScroll); </syntaxhighlight> <br> 下表に、デバウンスとスロットルの比較を示す。<br> <br> <center> {| class="wikitable" |+ デバウンス vs スロットルの比較 ! 特性 !! デバウンス !! スロットル |- | 実行タイミング || 最後のイベント後、指定時間経過後 || 指定時間ごとに最大1回 |- | 主な使用例 || 検索入力、フォーム検証 || スクロール、マウス移動 |- | 処理の遅延 || 入力停止まで遅延 || 固定間隔で定期実行 |- | ユースケース || 最終的な値が必要 || 定期的な監視が必要 |} </center> <br><br> == ReactのuseEffect内でのタイマ管理 == Reactコンポーネント内でタイマを使用する場合、コンポーネントのライフサイクルとタイマを適切に連動させる必要がある。<br> <br> コンポーネントがアンマウントされる時にタイマが解除されないと、メモリリークやアンマウント後のstate更新による警告が発生する。<br> <br> ==== クリーンアップ関数の重要性 ==== <code>useEffect</code> はクリーンアップ関数を返すことができる。<br> このクリーンアップ関数は、コンポーネントのアンマウント時や依存配列の値が変更される前に実行される。<br> <br> クリーンアップが必要な理由を以下に示す。<br> <br> * コンポーネントがアンマウント後もタイマが動作し続け、存在しないstateを更新しようとするとエラーになる。 * <code>setInterval</code> の場合、クリーンアップを忘れると、アンマウント後もコールバックが永続的に実行される。 * メモリリークによりアプリケーションのパフォーマンスが低下する。 <br> ==== setTimeout / setInterval の管理 ==== * <code>setTimeout</code> を <code>useEffect</code> 内で使用する例 *: <syntaxhighlight lang="javascript"> import { useEffect } from 'react'; function DelayedComponent() { useEffect(() => { const timerId = setTimeout(() => { console.log("Timeout executed"); }, 1000); // クリーンアップ関数でタイマをキャンセル return () => clearTimeout(timerId); }, []); return <div>Delayed component</div>; } </syntaxhighlight> *: <br> * <code>setInterval</code> を <code>useEffect</code> 内で使用する例 *: <syntaxhighlight lang="javascript"> import { useEffect, useState } from 'react'; function CounterComponent() { const [count, setCount] = useState(0); useEffect(() => { const intervalId = setInterval(() => { // 関数形式で更新することで、依存配列にcountを含めずに済む setCount(c => c + 1); }, 1000); // クリーンアップ関数でインターバルをキャンセル return () => clearInterval(intervalId); }, []); return <div>Count: {count}</div>; } </syntaxhighlight> <br> <code>setCount(c => c + 1)</code> のように関数形式で更新することにより、<code>count</code> を依存配列に含める必要がなくなり、不要なインターバルの再登録を防ぐことができる。<br> <br> ==== requestAnimationFrame の管理 ==== <code>requestAnimationFrame</code> をReactで使用する場合、<code>useEffect</code> の代わりに <code>useLayoutEffect</code> を使用することが推奨される。<br> <br> <code>useLayoutEffect</code> はDOM更新後、再描画前に同期的に実行されるため、アニメーションのタイミング問題を回避できる。<br> <br> <code>useLayoutEffect</code> と <code>requestAnimationFrame</code> を組み合わせた例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> import { useLayoutEffect, useRef } from 'react'; function AnimationComponent() { const elementRef = useRef(null); useLayoutEffect(() => { let animationId = null; let x = 0; function animate() { x += 2; if (elementRef.current) { elementRef.current.style.left = x + 'px'; } if (x < 500) { animationId = requestAnimationFrame(animate); } } animationId = requestAnimationFrame(animate); // クリーンアップ関数でアニメーションをキャンセル return () => { if (animationId) cancelAnimationFrame(animationId); }; }, []); return <div ref={elementRef} style={{ position: 'absolute' }}>Animated element</div>; } </syntaxhighlight> <br> <code>useLayoutEffect</code> を使用する場合の注意点を以下に示す。<br> * <code>useLayoutEffect</code> はサーバサイドレンダリング (SSR) では実行されないため、SSR環境では <code>useEffect</code> を使用するか、条件分岐が必要 * クライアントサイドのみで動作するアニメーションには <code>useLayoutEffect</code> が適している。 <br><br> == 関連情報 == * [https://developer.mozilla.org/ja/docs/Web/API/setTimeout MDN Web Docs - setTimeout] * [https://developer.mozilla.org/ja/docs/Web/API/clearTimeout MDN Web Docs - clearTimeout] * [https://developer.mozilla.org/ja/docs/Web/API/setInterval MDN Web Docs - setInterval] * [https://developer.mozilla.org/ja/docs/Web/API/clearInterval MDN Web Docs - clearInterval] * [https://developer.mozilla.org/ja/docs/Web/API/window/requestAnimationFrame MDN Web Docs - requestAnimationFrame] * [https://developer.mozilla.org/ja/docs/Web/API/HTML_DOM_API/Microtask_guide MDN Web Docs - マイクロタスクガイド] * [https://react.dev/reference/react/useEffect React 公式ドキュメント - useEffect] * [https://react.dev/reference/react/useLayoutEffect React 公式ドキュメント - useLayoutEffect] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Web]]
JavaScriptの基礎 - タイマ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse