ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptの動作仕組みと原理を完全マスター

JavaScriptの動作仕組みと原理を完全マスター

WBOY
WBOY転載
2022-04-25 17:05:203122ブラウズ

この記事では、javascript に関する関連知識をお届けします。主に、解析エンジンやその他のコンテンツを含む、JavaScript の動作メカニズムと原理に関する関連事項を紹介します。一緒に見ていきましょう。助けるために。

JavaScriptの動作仕組みと原理を完全マスター

[関連する推奨事項: JavaScript ビデオ チュートリアル Web フロントエンド ]

私はこれまで書いてきましたjs を 2 年以上使用していますが、その動作メカニズムや原理がよく理解できませんでした。今日は特別に達人の理論と私自身の要約を以下に記録しました:

JavaScript 解析エンジンとは

簡単に言えば、JavaScript 解析エンジンは、JavaScript コードを「読み取り」、コードの実行結果を正確に提供できるプログラムです。

たとえば、var a = 1 1; のようなコードを作成すると、JavaScript エンジンはコードを理解 (解析) し、a の値を 2 に変更します。

コンパイルの原理を勉強したことがある方は、静的言語 (Java、C、C など) の場合、上記のことを処理するものがコンパイラー (Compiler) と呼ばれることを知っています。 JavaScript などの動的言語の場合、コンパイラ、インタプリタと呼ばれます。 2 つの違いは 1 つの文に要約できます。コンパイラーはソース コードを別のタイプのコード (マシン コード、バイトコードなど) にコンパイルしますが、インタープリターはコードの実行結果を直接解析して出力します。たとえば、Firebug のコンソールは JavaScript インタープリターです。

ただし、たとえば V8 (Chrome の JS エンジン) は実際に JS の実行パフォーマンスを向上させるために JavaScript エンジンを使用しているため、JavaScript エンジンがインタプリタであるかコンパイラであるかを定義することは現在では困難です。 JS はネイティブ マシン コード (ネイティブ マシン コード) にコンパイルされ、その後マシン コードが実行されます (これははるかに高速になります)。

JavaScript 解析エンジンと ECMAScript の関係について

JavaScript エンジンもプログラムであり、私たちが記述する JavaScript コードもプログラムです。プログラムを理解させるにはどうすればよいでしょうか?これにはルールを定義する必要があります。たとえば、前述の var a = 1 1; の意味は次のとおりです:

左側の var は変数 a を宣言する宣言を表します
右側の var は 1 と 1 の違いを表しますand 1 Do add
真ん中の等号は、これが代入ステートメントであることを示します
最後のセミコロンはステートメントの終わりを示します
これらがルールです。 JavaScript エンジンは、この標準に従って JavaScript コードを解析できます。次に、ここの ECMAScript でこれらのルールを定義します。その中で、文書 ECMAScript 262 は、JavaScript 言語の完全な標準セットを定義しています。これらには以下が含まれます:

var、if、else、break、 continue などは JavaScript のキーワードです
abstract、int、long などは JavaScript の予約語です
それを数値として数える方法と文字列としてカウントする方法など
演算子 ( 、 - 、>、 JavaScript 構文を定義します
式、ステートメントなどの標準処理アルゴリズムを定義します。 == に遭遇した場合の対処方法
⋯⋯
標準の JavaScript エンジンは、この一連のドキュメントに従って実装されます。標準に従わない実装もあるため、ここでは標準が強調されていることに注意してください。 IEのJSエンジンなど。これが、JavaScript に互換性の問題がある理由です。 IE の JS エンジンが標準に従って実装されていない理由については、ブラウザ戦争に関係します。ここでは詳しく説明しませんので、自分でググってください。

つまり、簡単に言うと、ECMAScript が言語の標準を定義し、JavaScript エンジンがそれに従って実装する、これが 2 つの関係です。

JavaScript 解析エンジンとブラウザとの関係は何ですか?

簡単に言えば、JavaScript エンジンはブラウザのコンポーネントの 1 つです。ブラウザは、ページの解析、ページのレンダリング、Cookie 管理、履歴記録など、他にも多くのことを実行する必要があるためです。そうですね、JavaScript エンジンはコンポーネントであるため、通常はブラウザー開発者自身によって開発されます。例: IE9 の Chakra、Firefox の TraceMonkey、Chrome の V8 など。

また、ブラウザごとに異なる JavaScript エンジンが使用されていることがわかります。したがって、私たちが言えることは、どの JavaScript エンジンをより深く理解するかということだけです。

JavaScript がシングルスレッドである理由

JavaScript 言語の主な特徴の 1 つは、JavaScript 言語がシングルスレッドであること、つまり、同時に 1 つのことしか実行できないことです。 。では、なぜ JavaScript は複数のスレッドを持てないのでしょうか?これにより効率が向上します。

JavaScript の単一スレッドは、その目的に関連しています。ブラウザーのスクリプト言語としての JavaScript の主な目的は、ユーザーと対話して DOM を操作することです。これにより、シングルスレッドのみが可能であることが決まります。そうでない場合は、非常に複雑な同期の問題が発生します。たとえば、JavaScript に同時に 2 つのスレッドがあるとします。1 つのスレッドは特定の DOM ノードにコンテンツを追加し、もう 1 つのスレッドはノードを削除します。この場合、ブラウザはどちらのスレッドを使用する必要がありますか?

つまり、複雑さを避けるために、JavaScript は誕生以来シングルスレッドであり、これがこの言語の中核機能となっており、今後も変わることはありません。

マルチコア CPU のコンピューティング能力を活用するために、HTML5 は Web Worker 標準を提案しています。これにより、JavaScript スクリプトは複数のスレッドを作成できますが、子スレッドはメインスレッドによって完全に制御されるため、 DOM を操作しないでください。したがって、この新しい標準は JavaScript のシングルスレッドの性質を変更しません。

プロセスとスレッドの区別

プロセスは CPU リソース割り当ての最小単位であり、プロセスには複数のスレッドを含めることができます。ブラウザはマルチプロセスであり、開いているブラウザ ウィンドウはそれぞれ 1 つのプロセスです。

スレッドは CPU スケジューリングの最小単位であり、プログラムのメモリ空間は同じプロセス内のスレッド間で共有されます。

プロセスは倉庫と見なすことができ、スレッドは輸送可能なトラックです。各倉庫には、倉庫にサービスを提供する (商品を運ぶ) ための複数のトラックがあります。各倉庫は複数の車両で牽引できます。貨物ですが、各車両が一度にできることは 1 つだけで、それはこの貨物を輸送することです。

コアポイント:

プロセスは、CPU リソース割り当ての最小単位 (リソースを所有し、独立して実行できる最小単位)です。スレッドは CPU スケジューリングの最小単位です (スレッドはプロセスに基づくプログラム実行単位であり、プロセス内に複数のスレッドが存在する可能性があります)

異なるプロセスも通信できますが、コストが高くなります。

ブラウザはマルチプロセスです

プロセスとスレッドの違いを理解した後、ブラウザをある程度理解しましょう: (最初に簡略化した理解を見てみましょう)

ブラウザはマルチプロセスですプロセスの

ブラウザが実行できる理由は、システムがそのプロセスにリソース (CPU、メモリ) を割り当てるためです

簡単に理解すると、タブ ページを開くたびに、次の処理と同等になります。別のブラウザプロセスを作成します。

Chrome を例に挙げると、複数のタブ ページがあり、Chrome のタスク マネージャーで複数のプロセスを確認できます (各タブ ページには独立したプロセスとメイン プロセスがあります)。 Windows タスク マネージャーで。

注: ここでは、ブラウザにも独自の最適化メカニズムが必要です。複数のタブ ページを開いた後、Chrome タスク マネージャーで、いくつかのプロセスがマージされているのが確認できる場合があります (各タブ ラベルは、プロセスが存在しません)必然的に絶対的です)

ブラウザにはどのようなプロセスが含まれていますか?

ブラウザがマルチプロセスであることを理解した後、ブラウザにどのようなプロセスが含まれているかを見てみましょう: (理解を容易にするために、

(1) ブラウザ プロセス: ブラウザのメイン プロセスは 1 つだけです (調整と制御を担当します)。役割:

ブラウザ インターフェイスの表示とユーザー インタラクションを担当します。前方、後方など。
  • 各ページの管理、他のプロセスの作成と破棄を担当します。
  • レンダラー プロセスによって取得されたメモリ内のビットマップをユーザー インターフェイスに描画します
  • リソース、ダウンロードなどのネットワーク管理
  • (2) サードパーティのプラグイン プロセス: 各タイプのプラグインはプロセスに対応しており、このプロセスは、プラグインを使用します
(3) GPU プロセス: 最大 1 つ、3D 描画などに使用されます。

(4) ブラウザー レンダリング プロセス (ブラウザー カーネル、レンダラ プロセス、内部マルチスレッド): デフォルトでは、各タブ ページには 1 つのプロセスがあり、相互に影響しません。主な機能は次のとおりです: ページのレンダリング、スクリプトの実行、イベント処理など。

メモリの強化: ブラウザーで Web ページを開くことは、新しいプロセスを開始することと同じです (プロセスには独自のマルチスレッドがあります)

もちろん、ブラウザーは複数のプロセスをマージすることがあります (たとえば、複数の空白のタブを開いた後、複数の空白のタブが 1 つのプロセスにマージされていることがわかります)

複数のブラウザー プロセスの利点

単一プロセス ブラウザと比較して、マルチプロセス ブラウザには次の利点があります。

ブラウザ全体に影響を与える単一ページのクラッシュを回避します。
  • サードパーティのプラグインを回避します。ブラウザ全体に影響を与えるクラッシュ
  • 複数のプロセスがマルチコアの利点を最大限に活用する
  • サンドボックス モデルを便利に使用してプラグインや他のプロセスを分離し、ブラウザの安定性を向上させる
  • 簡単な理解:

ブラウザが単一プロセスの場合、特定のタブ ページがクラッシュするとブラウザ全体に影響があり、エクスペリエンスが低下します。同様に、単一プロセスの場合は、プラグインのクラッシュはブラウザ全体にも影響します。

もちろん、メモリやその他のリソースの消費も大きくなります。これは、空間が時間と交換されることを意味します。 Chrome ではメモリがいくら多くても足りません。メモリ リーク問題は少し改善されましたが、これは単なる改善であり、消費電力の増加にもつながります。

ブラウザ カーネル (レンダリング プロセス)

ここが重要なポイントです。上に挙げた非常に多くのプロセスがあることがわかります。つまり、通常のフロントエンド操作の場合、最終的に必要となるのは次のとおりです。何?答えはレンダリングプロセスにあります。

ページの描画、JSの実行、イベントループがすべてこのプロセス内で行われていることが分かります。次に、このプロセスの分析に焦点を当てます。

ブラウザのレンダリング プロセスはマルチスレッドであることに注意してください (JS エンジンはシングルスレッドです)

次に、次のことを考えてみましょう。どのスレッドが含まれているかを確認してください (主な常駐スレッドをいくつかリストします):

1. GUI レンダリング スレッド

  • は、ブラウザ インターフェイスのレンダリング、HTML と CSS の解析、DOM ツリーと RenderObject ツリー (単純に CSS によって形成されるスタイル ツリーとして理解されます) の構築を担当します。 Flutterのコアの一部)、レイアウト、描画など。
  • インターフェースを再描画する必要がある場合 (Repaint)、または特定の操作によってリフローが発生した場合、このスレッドが実行されます。
  • GUI レンダリング スレッドと JS エンジン スレッドは別のスレッドであることに注意してください。相互に排他的です。JS エンジンの実行中、GUI スレッドは一時停止 (フリーズ) され、GUI の更新はキューに保存され、JS エンジンがアイドル状態になるとすぐに実行されます。

2. JS エンジン スレッド

  • は JS カーネルとも呼ばれ、JavaScript スクリプト プログラムの処理を担当します。 (V8 エンジンなど)
  • JS エンジン スレッドは、JavaScript スクリプトの解析とコードの実行を担当します。
  • JS エンジンはタスク キュー内のタスクの到着を待ってから処理します。タブ ページ (レンダラー プロセス) で JS プログラムを実行する JS スレッドは常に 1 つだけです。
  • また、GUI レンダリング スレッドと JS エンジン スレッドは相互に排他的であるため、JS の実行時間が長すぎると、ページのレンダリングが一貫性を失い、ページのレンダリングと読み込みが中断されることにも注意してください。ブロックされること。

3. イベント トリガー スレッド

  • は、JS エンジンではなくブラウザーに属し、イベント ループの制御に使用されます (JS エンジン自体が忙しすぎるため、ブラウザが別のスレッドを開いて支援する必要があります)
  • JS エンジンが setTimeOut などのコード ブロックを実行したいとき (マウス クリックなど、ブラウザ カーネルの他のスレッドから実行されることもあります) Ajax 非同期リクエストなど)、対応するタスクがイベント スレッドに追加されます。そしてソートを担当します。
  • 対応するイベントがトリガー条件を満たしてトリガーされると、スレッドは処理対象のキューの最後にイベントを追加し、JS エンジンによる処理を待ちます
  • JS シングルスレッドの関係のため、保留キュー内のイベントは JS エンジンによる処理を待つためにキューに入れられる必要があることに注意してください (JS エンジンがアイドル状態のときに実行されます)
  • これは、多数のイベントと「イベント キュー」の管理を担当しており、アイドル状態のときにイベント キュー内のタスクのみが JS エンジンによって実行され、JS エンジンが行う必要があることは単純に理解できます。イベントがトリガーされたときにイベント キューに追加します。たとえば、マウスをクリックします。

4. タイミング トリガー スレッド

  • 伝説の setInterval と setTimeout が配置されているスレッド
  • ブラウザのタイミング カウンタは JavaScript エンジンによってカウントされません, (JavaScript エンジンはシングルスレッドであるため、スレッドがブロックされた状態になるとタイミングの精度に影響します)
  • したがって、タイミングの計測とトリガーには別のスレッドが使用されます。タイミングが完了すると、イベント キューに追加され (イベント トリガー スレッドの「イベントがトリガー条件を満たしてトリガーされた場合」に相当)、実行前に JS エンジンがアイドル状態になるのを待ちます。
  • W3C の HTML 標準では、setTimeout の 4ms 未満の時間間隔は 4ms としてカウントされると規定されていることに注意してください。

5. 非同期 http リクエスト スレッド

  • XMLHttpRequest が接続された後、ブラウザを通じて新しいスレッド リクエストが開かれます
  • ステータスの変化が検出されると、 、コールバック関数が設定されている場合、非同期スレッドは状態変更イベントを生成し、このコールバックをイベント キューに入れます。その後、JavaScript エンジンによって実行されます。

ブラウザプロセスとブラウザカーネル(レンダラープロセス)間の通信プロセス

これを見ると、まず、ブラウザプロセスとブラウザカーネルのプロセスとスレッドについてある程度理解できるはずです。次に、ブラウザのブラウザ プロセス (制御プロセス) がカーネルとどのように通信するかについて説明します。これを理解した後、この部分の知識を結び付けて、最初から最後まで完全な概念を得ることができます。

タスク マネージャーを開いてからブラウザーを開くと、タスク マネージャーに 2 つのプロセスが表示されます (1 つはメイン コントロール プロセスで、もう 1 つはタブ ページを開くレンダリング プロセスです)。次に、この前提の下で、プロセス全体を見てみましょう: (大幅に簡略化されています)

  • ブラウザ プロセスがユーザー リクエストを受信すると、最初にページ コンテンツ (次のような) を取得する必要があります。タスクは、RendererHost インターフェイスを介してレンダー (カーネル) プロセスに渡されます。
  • レンダラ プロセスのレンダラー インターフェイスは、メッセージを受信し、簡単に説明して、メッセージを渡します。
    # レンダリング スレッドはリクエストを受信し、Web ページをロードして Web ページをレンダリングします。これには、ブラウザ プロセスがリソースを取得し、GPU プロセスがリソースを取得する必要がある場合があります。 help render
  1. ## もちろん、DOM を操作する JS スレッドが存在する可能性があります (これにより、リフローと再描画が発生する可能性があります)
  2. 最後に、レンダリング プロセスが結果をブラウザ プロセスに渡します
ブラウザ プロセスは結果を受け取り、その結果を描画します。
  • これは簡単な図です: (非常に簡略化されています)


JavaScriptの動作仕組みと原理を完全マスター

ブラウザ カーネルにおけるスレッド間の関係

この時点で、ブラウザの動作についてはすでに学習しました。全体的な概念は作成されました。次に、いくつかの概念を簡単に整理しましょう

GUI レンダリング スレッドと JS エンジン スレッドは相互に排他的です

JavaScript は DOM を操作できるため、これらの要素のプロパティを変更し、同時にインターフェイス (つまり、JSスレッドと UI スレッドが同時に実行される場合)、前後のレンダリング スレッドで取得された要素データが不整合になる可能性があります。

そこで、ブラウザでは、予期しない描画結果を防ぐために、GUI 描画スレッドと JS エンジンが排他的な関係になるように設定し、JS エンジンが実行されると GUI スレッドが一時停止され、 GUI の更新はキューの待機に保存され、JS エンジン スレッドがアイドル状態のときにすぐに実行されます。

JS はページの読み込みをブロックします

上記の相互排他関係から、実行時間が長すぎる場合、JS はページをブロックすると推測できます。

たとえば、JS エンジンが膨大な量の計算を実行しているとします。この時点で GUI が更新されたとしても、それはキューに保存され、JS エンジンがアイドル状態になって実行されるのを待ちます。 。すると、膨大な計算量が発生するため、JS エンジンは長時間アイドル状態になる可能性が高く、当然のことながら非常に大きく感じられます。

したがって、JS の実行に時間がかかりすぎるとページのレンダリングに一貫性がなくなり、ページのレンダリングと読み込みがブロックされているように感じることを避けるようにしてください。

この問題を解決するには、計算をバックエンドに配置することに加えて、それが避けられず、膨大な計算が UI に関連している場合、私のアイデアは setTimeout を使用してタスクを分割し、途中で少し空き時間があります。ページが直接フリーズしないように、JS エンジンに UI を処理させる時間です。

必要な HTML5 の最小バージョンを直接決定する場合は、以下の WebWorker を参照してください。

WebWorker、JS のマルチスレッド

前の記事で述べたように、JS エンジンはシングルスレッドであり、JS の実行時間が長すぎる場合、ページをブロックしてしまうため、JS は実際に CPU を大量に使用する計算には無力なのでしょうか?

つまり、Web Worker は後に HTML5 でサポートされるようになりました。

MDN の公式説明は次のとおりです:

Web Worker は、Web コンテンツのバックグラウンド スレッドでスクリプトを実行する簡単な方法を提供します。

スレッドは、ユーザー インターフェイスに干渉することなくタスクを実行できます。ワーカーは、名前付きの JavaScript ファイルを実行するコンストラクター (Worker()) を使用して作成されたオブジェクトです (このファイルには、ワーカーで実行されるコードが含まれています)糸 )。

ワーカーは、現在のウィンドウとは異なる別のグローバル コンテキストで実行されます。

したがって、ウィンドウ ショートカットを使用して (self ではなく) 現在のグローバル スコープを取得すると、ワーカー内でエラーが返されます。

次のように理解してください。ワーカー、JS エンジンはブラウザに適用されてサブスレッドを開きます (サブスレッドはブラウザによって開かれ、メインスレッドによって完全に制御され、DOM を操作できません)

JS エンジン スレッドとワーカースレッドは特定のメソッド(postMessage API を渡す必要があります。特定のデータのスレッドと対話するためにオブジェクトをシリアル化する必要があります)を通じて通信します。

したがって、非常に時間のかかる作業がある場合は、別のワーカー スレッドを開いてください。それはどれほど驚くべきことでしょう。JS エンジンのメインスレッドには影響しません。結果が計算されるのを待つだけです。最後に、結果をメインスレッドに伝えるだけです。完璧です!

そして、JS エンジンのメインスレッドには影響しないことに注意してください。エンジンはシングルスレッドです。この本質は変わっていません。ワーカーは、ブラウザによって開かれる JS エンジン用のプラグインとして理解できます。特に、これらの大きなコンピューティングの問題を解決するために設計されています。

その他、Worker の詳細な説明はこの記事の範囲を超えているため、詳細は説明しません。

WebWorker と SharedWorker

ここまで来たら、もう一度 SharedWorker について触れてみましょう (後でこれら 2 つの概念が混同されないようにするため)

WebWorker のみに属します特定のページは、他のページのレンダリング プロセス (ブラウザ カーネル プロセス) と共有されません。

したがって、Chrome は、ワーカー プログラムで JavaScript を実行するために、レンダリング プロセス (各タブ ページはレンダリング プロセス) に新しいスレッドを作成します。 。

SharedWorker はブラウザのすべてのページで共有され、レンダリング プロセスに関連付けられておらず、複数のレンダリング プロセスで共有できるため、ワーカーと同じ方法で実装することはできません。
したがって、Chrome ブラウザは別のSharedWorker プロセスは JavaScript プログラムの実行に使用されます。何度作成されても、ブラウザ内の同じ JavaScript ごとに SharedWorker プロセスは 1 つだけ存在します。
これを見るとわかりやすいと思いますが、本質的にはプロセスとスレッドの違いです。 SharedWorker は独立したプロセスによって管理され、WebWorker は Render プロセスの下の単なるスレッドです。

ブラウザ レンダリング プロセス

ブラウザ レンダリング プロセスの補足 (簡易バージョン)

理解を容易にするために、事前作業は直接省略されています。ブラウザが URL を入力すると、ブラウザのメイン プロセスが引き継ぎ、ダウンロード スレッドを開き、次に http リクエストを作成し (DNS クエリや IP アドレス指定などを省略します)、応答を待ってコンテンツを取得し、コンテンツを転送します。

ブラウザのレンダリング プロセスが開始されます

ブラウザ カーネルがコンテンツを取得した後、レンダリングは次の手順に大まかに分けられます。

HTML を解析して dom ツリーを構築する
css を解析してレンダー ツリーを構築する (CSS コードをツリー状のデータ構造に解析し、それを DOM と結合してレンダー ツリーにマージします)
要素のサイズと位置の計算を担当するレンダー ツリーのレイアウト (レイアウト/リフロー)
レンダー ツリー (ペイント) の描画とページのピクセル情報の描画
ブラウザーは各レイヤーの情報を GPU に送信し、 GPU は各レイヤーを合成して画面に表示します。
詳細な手順は省略していますが、レンダリング完了後はloadイベントとなり、その後は独自のJSロジック処理となります。

一部の詳細な手順が省略されているため、注意が必要な点についていくつか説明します。
JavaScriptの動作仕組みと原理を完全マスター

load イベントと DOMContentLoaded イベントのシーケンス

前述したように、load イベントはレンダリング完了後にトリガーされますが、load イベントのシーケンスを区別できますか?とDOMContentLoadedイベント??

これは非常に簡単です。定義を知っておくだけです。

DOMContentLoaded イベントがトリガーされるときは、DOM が読み込まれるときのみであり、スタイル シート、画像、非同期スクリプトなどは除きます。
onload イベントがトリガーされると、ページ上のすべての DOM、スタイル シート、スクリプト、画像が読み込まれ、つまりレンダリングされます。 # #css の読み込みが dom ツリーのレンダリングをブロックするかどうか

ここで話しているのは、css がヘッダーに導入される状況です

まず、css が非同期でダウンロードされることは誰もが知っています。別のダウンロード スレッドによって。

次に、次の現象について説明します。

css の読み込みによって DOM ツリーの解析がブロックされることはありません (DOM は非同期読み込み中に通常どおり構築されます)

    しかし、ブロックされることはあります。ブロック レンダー ツリー レンダリング (レンダー ツリーには CSS 情報が必要なので、レンダリング時に CSS が読み込まれるまで待つ必要があります)
  • これもブラウザの最適化メカニズムである可能性があります。 CSS をロードするときに、以下の DOM ノードのスタイルを変更する可能性があるため、CSS のロードによってレンダー ツリーのレンダリングがブロックされない場合は、CSS のロード後にレンダー ツリーを再描画またはリフローする必要がある場合があります。多少の問題は発生しますが、必要な損失はありません。
したがって、最初に DOM ツリーの構造を解析し、実行できる作業を完了してから、CSS がロードされるのを待ってから、最終的なスタイルに従ってレンダー ツリーをレンダリングするだけです。パフォーマンス、このアプローチは確かに良くなります。

通常レイヤーとコンポジットレイヤー

コンポジットの概念については、レンダリングのステップで説明します。

ブラウザによってレンダリングされるレイヤーには、通常、通常のレイヤーと複合レイヤーの 2 つのカテゴリが含まれることが簡単に理解できます。

まず、通常のドキュメント ストリームは、複合レイヤー (これはデフォルトの複合レイヤーと呼ばれます。それに追加される要素の数に関係なく、それらは実際には同じ複合レイヤー内にあります)

2 つ目は、絶対レイアウト (固定の場合も同様) です。通常のドキュメント フローから分離されていますが、依然としてデフォルトの複合レイヤーに属しています。

次に、ハードウェア アクセラレーションを通じて新しい複合レイヤーを宣言できます。これにより、リソースが個別に割り当てられます (もちろん、通常のドキュメント フローからも分離されるため、複合レイヤーがどのように変更されても、デフォルトの複合レイヤーのリフロー再描画には影響しません)

これは簡単に理解できます: GPU では、各複合レイヤーは個別に描画されるため、相互に影響を与えません。そのため、一部のシーンのハードウェア アクセラレーション効果が発生します。最初のレベルは素晴らしいです。

Chrome DevTools --> その他のツール --> レンダリング --> レイヤー境界線で確認できます。黄色のレベルは複合レイヤー情報です。

複合レイヤーに変換する方法 (ハードウェア アクセラレーション)

この要素を複合レイヤーに変換します。これは伝説的なハードウェア アクセラレーション テクノロジです。
最も一般的に使用される方法:translate3d、translateZ

opacity 属性/トランジション アニメーション (アニメーションの実行中にコンポジション レイヤーが作成され、アニメーションが開始または終了しなかった後、要素は以前の状態に戻ります)

will-chang 属性 (これは比較的リモートです)一般に不透明度や翻訳と組み合わせて使用​​されます。その機能は、ブラウザーが何らかの最適化作業を開始できるように、変更が加えられることを事前にブラウザーに伝えることです (使用後にこれを解放するのが最善です)

video、iframe、canvas、webgl などの要素
その他、以前のフラッシュ プラグインなど
絶対アクセラレーションとハードウェア アクセラレーションの違い

絶対は分離できますが、通常のドキュメント フローからは、デフォルトの複合レイヤーから分離できません。そのため、通常のドキュメントフローでは絶対値の情報が変わってもレンダーツリーは変わりませんが、最終的にブラウザが描画する際には合成レイヤー全体が描画されるため、絶対値の情報の変更は描画に影響を及ぼします。複合層全体の。 (ブラウザが再描画します。複合レイヤーのコンテンツが多い場合、absolute によってもたらされる描画情報が変化しすぎて、リソースの消費が非常に大きくなります)

そして、ハードウェア アクセラレーションは、別の複合レイヤー内で直接作成されるため (最初から開始)、その情報の変更はデフォルトの複合レイヤーには影響しません (もちろん、内部的には独自の複合レイヤーに影響します) が、最終的な構成 (出力ビュー) をトリガーするだけです。
複合レイヤーの役割

一般に、要素はハードウェア アクセラレーションが有効になった後、通常のドキュメント フローから独立した複合レイヤーになります。変更後、全体の再描画を回避できます。ページを作成してパフォーマンスを向上させますが、多数の複合レイヤーを使用しないようにしてください。そうしないと、過度のリソース消費によりページがスタックしてしまいます。

ハードウェア アクセラレーションを使用する場合はインデックスを使用してください

ハードウェア アクセラレーションを使用する場合は、可能な限りインデックスを使用して、これを防ぐ必要があります。デフォルトでは、ブラウザは後続の要素に対して複合レイヤー レンダリングを作成します。具体的な原則は次のとおりです: Webkit CSS3 では、この要素にハードウェア アクセラレーションが追加されており、インデックス レベルが比較的低い場合、この要素の背後にある他の要素は (レベルがこの要素より高いか同じで、相対属性または絶対属性が同じである場合)、デフォルトで複合レイヤーのレンダリングになります。これが適切に処理されない場合、これはパフォーマンスに大きな影響を与えます。

簡単に理解すると、実際にそれが可能です。これは暗黙的な合成概念であると考えられます。つまり、a が複合レイヤーで、b が a の上にある場合、b もまた複合レイヤーになります。これには特別な注意が必要です。

EventLoop からの JS の実行メカニズムについて話します

この時点では、ブラウザ ページの最初のレンダリングと実行メカニズムの分析がすでに完了しています。 JSエンジンの。

実行可能コンテキスト、VO、scop チェーンなどの概念については説明しないことに注意してください (これらは別の記事にまとめることができます) ここでは主に、JS コードがイベント ループと組み合わせてどのように実行されるかについて説明します。

このパートを読むための前提条件は、JS エンジンがシングルスレッドであることをすでに知っていることです。ここでは、上で説明したいくつかの概念が使用されます:

JS エンジン スレッド
  • イベント トリガー スレッド
  • 時間指定トリガー スレッド
  • 次に、概念を理解します。

  • JS は同期タスクと非同期タスクに分かれます

# #同期タスクはメイン スレッドで実行され、実行スタックを形成します。

    メイン スレッドの外側で、イベント トリガー スレッドがタスク キューを管理します。非同期タスクの実行結果がある限り、イベントが配置されます。タスクキュー内。
  • 実行スタック内のすべての同期タスクが実行されると (この時点では JS エンジンはアイドル状態です)、システムはタスク キューを読み取り、実行可能な非同期タスクを実行可能スタックに追加して、実行を開始します。
  • 図を見てください:


これを見ると、setTimeout によってプッシュされたイベントが時間通りに実行されないことがあるのはなぜですか?メインスレッドがまだアイドル状態ではなく、イベントリストにプッシュされるときに他のコードを実行している可能性があるため、当然エラーが発生します。 JavaScriptの動作仕組みと原理を完全マスター

イベント ループのメカニズムをさらに補足します。

上の図の大まかな説明は次のとおりです。 JavaScriptの動作仕組みと原理を完全マスター

メイン スレッドは次のようになります。実行中に実行スタックを生成します。スタック内のコードが特定の API を呼び出すと、イベント キューにさまざまなイベントが追加されます (ajax リクエストの完了など、トリガー条件が満たされたとき)

    スタック内のコードが実行されると、イベント キュー内のイベントが読み取られ、それらのコールバックが実行されます。
  • スタック内のコードが完了するまで常に待機する必要があることに注意してください。イベント キュー内のイベントを読み取る前に
タイマーについては個別に説明しましょう

##上記のイベント ループ メカニズムの核心は次のとおりです: JS エンジン スレッドとイベント トリガー スレッド

##しかし、イベント内に隠された詳細がいくつかあります。たとえば、setTimeout を呼び出した後、イベント キューに追加する前に特定の時間待機する方法はありますか?

JS エンジンによって検出されますか?もちろん違います。これはタイマー スレッドによって制御されます (JS エンジン自体が多忙で時間がないため)

なぜ別のタイマー スレッドが必要なのでしょうか? JavaScript エンジンはシングルスレッドであるため、スレッドがブロックされた状態になるとタイミングの精度に影響するため、タイミングのために別のスレッドを開く必要があります。

タイマー スレッドはいつ使用されますか? setTimeout または setInterval を使用する場合、タイマー スレッドで時間を計測する必要があり、時間が完了すると、特定のイベントがイベント キューにプッシュされます。

setInterval の代わりに setTimeout

setTimeout を使用して通常のタイミングをシミュレートすることと、setInterval を直接使用することには違いがあります。

setTimeout が実行されるたびに実行され、一定期間実行した後に setTimeout が続行されるため、途中でエラーが発生します (エラーはコードの実行時間に関連します)

そしてsetInterval イベントを毎回正確にプッシュするためですが、実際のイベントの実行時間は正確ではなく、イベントが完了する前に次のイベントが来る可能性があります。

そして、setInterval にはいくつかの致命的な問題があります:

累積的な影響として、setInterval コードが再度キューに追加される前に実行が完了していない場合、タイマー コードが間隔なしで連続して数回実行されます。通常の間隔で実行したとしても、複数の setInterval コードの実行時間が予想より短くなる場合があります (コードの実行にある程度の時間がかかるため)
たとえば、iOS の Webview や Safari などのブラウザには、スクロール時 JSを実行しない場合、setIntervalを使用すると、スクロール後に複数回実行されることがわかります スクロールではJSが実行されないため、コールバックが蓄積されコンテナがフリーズし、不明なエラーが発生しますコールバックの実行時間が長すぎる場合 (このセクションは後で補足します。SetInterval の組み込みの最適化によりコールバックが繰り返し追加されることはありません)
そして、ブラウザーが最小化されて表示されている場合、setInterval はプログラムを実行せず、キュー内の setInterval のコールバック関数。ブラウザ ウィンドウが再度開かれると、すべてが瞬時に実行されます
したがって、非常に多くの問題を考慮して、現在一般的に考えられている最善の解決策は、 setTimeout を使用してシミュレートすることです。 setInterval、または特別な場合には requestAnimationFrame

を直接使用します。: JS 昇格で説明したように、JS エンジンは setInterval を最適化します。現在のイベント キューに setInterval コールバックがある場合、それは繰り返し追加されません。

高度なイベント ループ: マクロタスクとマイクロタスク

上記は JS イベント ループ メカニズムを整理したもので、ES5 の場合はこれで十分です。しかし、ES6 が普及した今でも、次のような問題が発生するでしょう:

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');

うーん、正しい実行順序は次のとおりです:

script start
script end
promise1
promise2
setTimeout

Why? ? Promise には microtask

という新しい概念があるため、さらに JS はマクロタスクとマイクロタスクの 2 つのタスクタイプに分けられますが、ECMAScript ではマイクロタスクをジョブと呼び、マクロタスクをタスクと呼ぶこともあります。

それらの定義は何ですか?違い?簡単に言うと、次のように理解できます:

1. マクロタスク (マクロタスクとも呼ばれます)

実行スタックによって毎回実行されるコードがマクロであることが理解できますタスク (イベントからの毎回の時間を含む) イベント コールバックをキューから取得し、実行のために実行スタックに入れる)

  • 各タスクはタスクを最初から最後まで実行し、他のタスクは実行しません
  • これにより、JS 内部タスクと DOM タスクを順序よく実行できるようになります。1 つのタスクの実行が終了した後、次のタスクの実行が開始される前にページが再レンダリングされます (タスク -> レンダリング -> タスク -> ...)
2. マイクロタスク (マイクロタスクとも呼ばれます)

理解できます現在のタスクの実行終了直後に実行されるタスクであること

  • つまり、現在のタスクの後、次のタスクの前、レンダリング前に
  • ##,レンダリングを待つ必要がないため、応答速度は setTimeout (setTimeout はタスク) よりも速くなります。
  • つまり、特定のマクロタスクが実行された後、その実行中に生成されたすべてのマイクロタスクは、
3. マクロタスクとマイクロタスクの形成
#3. マクロタスクとマイクロタスクの形成
  • #マクロタスク: メイン コード ブロック、setTimeout、setInterval など (各イベントイベント キューはマクロタスクです)
  • microtask: Promise、process.nextTick Wait

追加: ノード環境では、process.nextTick の優先順位が Promise よりも高くなります。これは単純に次のように理解されます: マクロ タスクが終了した後、マイクロタスク キューの nextTickQueue 部分が最初に実行され、次にマイクロタスクの Promise 部分が実行されます。

スレッドに基づいて理解してみましょう:

(1) マクロタスク内のイベントはイベント キューに配置され、このキューはイベント トリガー スレッドによって維持されます

(2) ) マイクロタスク内のすべてのマイクロタスクはマイクロタスク キュー (ジョブ キュー) に追加され、現在のマクロタスクが完了した後の実行を待機します。このキューは JS エンジン スレッドによって維持されます (これは私自身の理解から推測したものです。メインスレッド下でシームレスに実行されます)
動作メカニズムを要約すると、

  • マクロ タスクを実行します (スタック上にない場合は、イベント キューから取得します)
  • 実行中にマイクロタスクが見つかった場合は、マイクロタスクのタスク キューに追加します。
  • マクロタスクの実行後、現在のマイクロタスク キュー内のすべてのマイクロタスクがすぐに実行されます (順番に実行されます)
  • 現在のマクロ タスクが実行された後、レンダリングのチェックが開始され、その後 GUI スレッドがレンダリングを引き継ぎます
  • レンダリングが完了した後、JS スレッドが引き続き引き継ぎ、次のマクロ タスク (イベント キューから取得)

図に示すように:
JavaScriptの動作仕組みと原理を完全マスター

さらに、Promise ポリフィルとの違いに注意してください。および正式バージョン:

正式バージョンでは、これは標準のマイクロタスク形式です
Polyfill は一般に setTimeout を通じてシミュレートされるため、マクロタスクの形式になります
一部のブラウザでは実行が異なることに注意してください標準ブラウザでのシナリオ (ただし、一部のブラウザは標準ではない可能性があることに注意してください)

补充:使用MutationObserver实现microtask

MutationObserver可以用来实现microtask (它属于microtask,优先级小于Promise, 一般是Promise不支持时才会这样做)

它是HTML5中的新特性,作用是:监听一个DOM变动, 当DOM对象树发生任何变动时,Mutation Observer会得到通知

像以前的Vue源码中就是利用它来模拟nextTick的, 具体原理是,创建一个TextNode并监听内容变化, 然后要nextTick的时候去改一下这个节点的文本内容, 如下:

var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))

observer.observe(textNode, {
    characterData: true
})
timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
}

不过,现在的Vue(2.5+)的nextTick实现移除了MutationObserver的方式(据说是兼容性原因), 取而代之的是使用MessageChannel (当然,默认情况仍然是Promise,不支持才兼容的)。

MessageChannel属于宏任务,优先级是:MessageChannel->setTimeout, 所以Vue(2.5+)内部的nextTick与2.4及之前的实现是不一样的,需要注意下。

【相关推荐:javascript视频教程web前端

以上がJavaScriptの動作仕組みと原理を完全マスターの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。