js で 0ms 遅延タイマーを実装するいくつかの方法

js で 0ms 遅延タイマーを実装するいくつかの方法

ここ二日間、「タイムリーな setTimeout を実装するには?」を紹介する記事を見ました。 》、この記事はインタビューの質問から生まれました: setTimeout を時間どおりに実行する方法はありますか?詳細については、付録 [1] を参照してください。これを読んだ後、setTimeout 関数を詳しく調べてみたくなったので、MDN の関連ドキュメントを再度確認しました。[最小遅延 >= 4ms] と記載されていたため、setTimeout を使用して 0ms の遅延タイマーを実装することはできません。実装したい場合は、参考リンク [2] を参照してください。著者の実装アイデアは、postMessage を通じてシミュレートし、setTimeout の制限を回避して 0ms の遅延タイマーを実装することです。簡単に言うと、マクロタスクを開始してコールバックを実行することです。どのように実装されているかを見てみましょう。

(関数() {
 var タイムアウト = [];
 var messageName = "ゼロタイムアウトメッセージ";
 // setTimeoutと同様だが、関数引数のみを取る。
 // 時間引数なし(常にゼロ)および引数なし(クロージャを使用する必要があります)
 関数setZeroTimeout(fn) {
  タイムアウトをプッシュします(fn);
  window.postMessage(メッセージ名、"*");
 }
 関数handleMessage(イベント) {
  if (event.source == window && event.data == messageName) {
   イベントの伝播を停止します。
   タイムアウトの長さが0より大きい場合
    var fn = timeouts.shift();
    関数fn();
   }
  }
 }

 window.addEventListener("メッセージ", handleMessage, true);

 // ウィンドウ オブジェクトに追加したいものを 1 つ追加します。
 ウィンドウをゼロタイムアウトに設定します。
})();

作者はデモページも公開している[3]。setTimeout(0)と比較すると、私のブラウザでの実行結果は以下のようになる。

setZeroTimeout の 100 回の反復には 15 ミリ秒かかりました。
setTimeout(0) の 100 回の反復には 488 ミリ秒かかりました。

比較結果によると、setZeroTimeout は setTimeout よりも数百倍高速に実行され、大きな改善が見られます。本日お話ししたいのは、上記の方法に加えて、0 ミリ秒の遅延タイマーを実装するためにどのような他の方法が使用できるかということです。まず、カスタム タイマーが非同期であることを確認する必要があります。次に、できるだけ早く実行されるようにします。非同期性について言えば、js はいくつかのソリューションを提供しており、それを 1 つずつ検証することができます。

さまざまな実装方法について詳しく説明する前に、合意によって提供される setTimeout 比較バージョンは次のとおりです。カスタム実装スキームの実行時間を setTimeout バージョンと比較します。コードは比較的単純です。

(関数() {
 i = 0 とします。
 定数開始 = Date.now();
 関数テスト() {
  i++ < 100の場合{
   テストタイムアウトを設定します。
  } それ以外 {
   console.log('setTimeout 実行時間:', Date.now() - 開始);
  }
 }
 テストタイムアウトを設定します。
})();

キューマイクロタスク

queueMicrotask API を使用すると、マイクロタスクを追加できます。使い方は比較的簡単です。コールバック関数を渡すだけです。具体的な実装は次のとおりです。

(関数() {
 関数setZeroTimeout(fn) {
  キューマイクロタスク(fn);
 }
 i = 0 とします。
 定数開始 = Date.now();
 関数テスト() {
  i++ < 100の場合{
   setZeroTimeout(テスト);
  } それ以外 {
   console.log('setZeroTimeout 実行時間:', Date.now() - 開始);
  }
 }
 setZeroTimeout(テスト);
})();

setTimeout バージョンと比較すると、最終結果は次のようになります。

setZeroTimeout 実行時間: 2
setTimeout 実行時間: 490

この API については MDN に詳しい紹介があるので、ここでは詳しく説明しません。仕様書によると、ほとんどの場合、requestAnimationFrame() や requestIdleCallback() などの API を使用することをお勧めします。queueMicrotask はレンダリングをブロックするため、多くの場合これは良い方法ではありません。

非同期/待機

async/await はフロントエンド開発者にとってすでに不可欠であり、ここでもこれを使用して次のことを実現できます。

(関数() {
 非同期関数setAsyncTimeout(fn) {
  Promise.resolve().then(fn);
 }
 i = 0 とします。
 定数開始 = Date.now();
 非同期関数テスト(){
  (i++ < 100) の場合 {
   setAsyncTimeout(テスト) を待機します。
  } それ以外 {
   console.log('setAsyncTimeout 実行時間:', Date.now() - 開始);
  }
 }
 setAsyncTimeout(テスト);
})();

setTimeout バージョンと比較すると、最終結果は次のようになります。

setAsyncTimeout 実行時間: 2
setTimeout 実行時間: 490

面倒でも構わないのであれば、Promise を使用して実装することもできます。実際、それらはすべて似ており、コードが少し増えるだけなので、ここでは省略します。

メッセージチャネル

MessageChannel を使用すると、新しいメッセージ チャネルを作成し、その 2 つの MessagePort プロパティを通じてデータを送信できます。MessageChannel は、ワーカー/iframe 間の通信など、ポート間の通信を実現するためのポートの概念を提供します。

(関数() {
 定数チャンネル = 新しいメッセージチャンネル();
 関数setMessageChannelTimeout(fn) {
  チャネルポート2のpostMessage(null);
 }
 チャネル.port1.onmessage = 関数() {
    テスト();
 };
 i = 0 とします。
 定数開始 = Date.now();
 関数テスト() {
  i++ < 100の場合{
   メッセージチャネルタイムアウトを設定します(テスト)。
  } それ以外 {
   console.log('setMessageChannelTimeout 実行時間:', Date.now() - 開始);
  }
 }
 メッセージチャネルタイムアウトを設定します(テスト)。
})();

setTimeout バージョンと比較すると、最終結果は次のようになります。

setMessageChannelTimeout 実行時間: 4
setTimeout 実行時間: 490

3 番目の方法は、MessageChannel がマクロタスクを生成するのに対し、他の 2 つの方法はマイクロタスクであるため、前の 2 つの方法よりも実行に時間がかかります。マイクロタスクが最初に実行され、メイン スレッドをブロックするため、時間がかかります。

やっと

この記事では、js が提供する非同期ソリューションに基づいた 3 つの実装方法を紹介します。実装自体は複雑ではありません。

付録

【1】https://mp.weixin.qq.com/s/QRIXBoKr2dMgLob3Atq9-g
【2】https://dbaron.org/log/20100309-faster-timeouts
【3】https://dbaron.org/mozilla/zero-timeout

これで、js で 0ms 遅延タイマーを実装するいくつかの方法についての記事は終了です。より関連性の高い js 遅延タイマー コンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、次の関連記事を引き続き参照してください。今後も 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • JavaScriptタイマーの詳細な説明
  • JavaScript タイマーの詳細
  • 期間限定フラッシュセール機能を実現するJavaScriptタイマー
  • 画像のシームレスなスクロールを実現する JavaScript タイマー
  • JSタイマーを使用して要素を移動する
  • JavaScript タイマー原理の詳細な説明

<<:  CentOS 7.2 に SuPHP をインストールするための詳細な手順

>>:  macOS での MySQL 8.0.16 のインストールと設定のグラフィック チュートリアル

推薦する

HTML リスト タグ dl、ul、ol の使用例

コードをコピーコードは次のとおりです。 <!--リストタグ: <dl>: 階層リス...

js 学習ノート: class、super、extends キーワード

目次序文1. es6の前にオブジェクトを作成する2. es6 後のクラス宣言3. クラスの継承4. ...

Angularコンポーネントライフサイクルの詳細説明(I)

目次概要1. フックの呼び出し順序2. onChangesフック3. 変更検出メカニズムとDoChe...

HTML における src と href の違いについての簡単な説明

簡単に言うと、srcは「このリソースをロードしたい」という意味で、hrefは「このリソースに関連付け...

html の img src="" で js 関数または js 変数を呼び出して、画像パスを動的に指定します。

この問題に関して、オンライン リソースをたくさん見つけました。ここにいくつかの方法を示します。コード...

Linux環境にMySQLデータベースをインストールする詳細なチュートリアル

1. データベースをインストールする1) yum -y install mysql-server (...

MySQL パフォーマンスの最適化: インデックスを効率的かつ正しく使用する方法

実践こそが真実をテストする唯一の方法です。この記事では、インデックスの全体的な使用法についてのみ説明...

vue3 統合 API における vue2 の $refs の代替方法についての簡単な説明

vue2 プロジェクト開発の経験があれば、$refs に精通しているでしょう。 vue3 の急激なア...

MySQL マスタースレーブ構築(複数のマスターと 1 つのスレーブ)の実装アイデアと手順

背景:最近、同社のプロジェクトは同時実行のプレッシャーに耐えられないようなので、最適化が差し迫ってい...

MySQLデータベースインデックスの欠点と適切な使用

目次インデックスの適切な使用1. 通常のインデックスのデメリット2. 主キーインデックスの落とし穴3...

HTML チュートリアル: よく使われる HTML タグのコレクション (4)

導入された HTML タグは、必ずしも XHTML 仕様に完全に準拠しているわけではありません。実際...

Zabbix 5.0 ディスク自動検出と読み取り/書き込み監視の問題を分析する

ディスクを自動的に検出する構成キーの値注: このキー値は Linux プラットフォームでのみサポート...

Dockerでイメージ情報を表示する方法

この記事では、Dockerでイメージ情報を表示する方法を学ぶ必要があります。 1. imagesコマ...

CSS は、小さな鋭角のチャット ダイアログ ボックスで鋭角の吹き出し効果を実現します。

1. CSS を使用して、小さな尖った角のチャット ダイアログ ボックスと尖った角の吹き出しを描画...