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 のインストールと設定のグラフィック チュートリアル

推薦する

TypeScriptはvscodeを使用してコードのコンパイルプロセスを監視します

インストールtsコマンドをグローバルにインストールする npm install -g typescr...

JS ES6における構造化分解についてお話しましょう

概要es6 では、配列またはオブジェクトから指定された要素を取得する新しい方法が追加されました。これ...

JSはclip-pathを使用して動的領域クリッピング機能を実装します

背景今日、CodePen を閲覧していたところ、非常に興味深い効果を見つけました。 CodePen ...

仕事でよく使うLinuxコマンドまとめ

仕事では、docker や kubernetes などのオープンソース ツールをさらに活用しましょう...

Mac で MySQL 8.0.22 のパスワードを取得する方法

Mac 最新バージョンの MySQL 8.0.22 パスワード回復問題の説明:昨日、突然、Macで最...

Mysql の主キーと一意キーの違いのまとめ

主キーとは何ですか?主キーは、テーブル内の各タプル (行) を一意に識別するテーブル内の列です。主キ...

MySQL スレーブ ライブラリ Seconds_Behind_Master 遅延の概要

目次MySQL スレーブ ライブラリ Seconds_Behind_Master 遅延の概要1. 遅...

MySQL の選択、挿入、更新バッチ操作ステートメントのコード例

プロジェクトでは、データを操作するためにバッチ操作ステートメントが必要になることがよくあります。バッ...

Linux で AutoFs マウント サービスをインストールするチュートリアル

Samba サービスでも NFS サービスでも、サーバーの起動時にリモート共有リソースが自動的にマ...

Linux および CentOS (サーバー) に zip および unzip コマンド機能をインストールする

Linux に zip 解凍機能をインストールする通常、 zip コマンドは Linux サーバーに...

MySQL 5.7.21 のインストールと設定のチュートリアル

mysql5.7.21の簡単なインストール構成は次のとおりです。 1. MySQLのインストール1....

Layuiはログインインターフェース検証コードを実装します

この記事の例では、ログインインターフェース検証コードを実装するためのlayuiの具体的なコードを参考...

CentOS 7.4 に MySQL 5.7 を手動でインストールする方法

MySQL データベースは、特に JAVA プログラマーの間で広く使用されています。クラウド データ...

経験豊富な人が、プロフェッショナルで標準化されたMySQL起動スクリプトの開発方法を紹介します。

シェル スクリプト言語は、すべてのプログラミング言語の中で最も単純な言語であるため、資格のある Li...