序文
モバイルインターネットの時代において、ユーザーはウェブページのオープン速度に対する要求をますます高くしています。Baidu User Experience Departmentの調査によると、ページの放棄率とページのオープン時間の関係が下の図に示されています。
Baiduユーザーエクスペリエンス部門の調査結果によると、一般ユーザーが期待して受け入れることができるページの読み込み時間は3秒以内です。ページの読み込み時間が遅すぎる場合、ユーザーは忍耐力を失い、離れることを選択します。
ユーザーに対面する最初の画面として、最初の画面の重要性は自明です。ユーザーエクスペリエンスの最適化は、フロントエンド開発で注力する必要があることの1つです。
この記事では、8つのインタビューの質問を通じて、ブラウザーのレンダリングプロセスとパフォーマンスの最適化について説明します。
ブラウザのレンダリングプロセスを理解するために、最初にこれらの8つの質問を見てみましょう。後で解決策を提供します〜
-
なぜJavascriptはシングルスレッドなのですか?
太字 -
JSがページの読み込みをブロックするのはなぜですか?
-
CSSの読み込みによりブロッキングが発生しますか?
-
DOMContentLoadedとloadの違いは?
-
CRP、つまりクリティカルレンダリングパスとは何ですか?最適化する方法は?
-
遅延と非同期の違いは何ですか?
-
ブラウザのリフローと再描画について話しますか?
プロセスとスレッド
プロセスとスレッドは、オペレーティングシステムの基本的な概念です。
进程是 CPU 资源分配的最小单位(是能拥有资源和独立运行的最小单位)。
线程是 CPU 调度的最小单位(是建立在进程基础上的一次程序运行单位)。
最新のオペレーティングシステムでは、複数のタスクを同時に実行できます。たとえば、ブラウザーでインターネットをサーフィンしながら音楽を聴くことができます。
对于操作系统来说,一个任务就是一个进程,
たとえば、ブラウザを開くとブラウザプロセスが開始され、Wordを開くとWordプロセスが開始されます。
一部のプロセスは、Wordなど、同時に複数の処理を実行します。Wordは、入力、スペルチェック、印刷などを同時に実行できます。在一个进程内部,要同时做多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程。
各プロセスは少なくとも1つのことを行う必要があるため、プロセスには少なくとも1つのスレッドがあります。システムは各プロセスに独立したメモリを割り当てるため、プロセスには独自の独立したリソースがあります。同じプロセス内の各スレッドは、プロセスのメモリ空間(コードセグメント、データセット、ヒープなどを含む)を共有します。
鮮明なメタファーを借りると、プロセスは境界のある生産工場のようなものであり、スレッドは工場内の従業員のようなものです。彼らは自分のことをしたり、互いに協力して同じことをしたりできます。
アプリケーションを起動すると、コンピュータがプロセスを作成し、オペレーティングシステムがメモリの一部をプロセスに割り当て、アプリケーションのすべての状態がこのメモリに格納されます。
アプリケーションは、作業を支援するために複数のスレッドを作成することもできます。これらのスレッドは、メモリのこの部分のデータを共有できます。アプリケーションが閉じている場合、プロセスは終了し、オペレーティングシステムは関連するメモリを解放します。
ブラウザのマルチプロセスアーキテクチャ
優れたプログラムは、多くの場合、独立していて互いに連携しているいくつかのモジュールに分割されており、ブラウザもそうです。
Chromeを例にとると、Chromeは複数のプロセスで構成されており、それぞれに独自の中心的な責任があります。これらのプロセスは互いに連携して、ブラウザの全体的な機能を完了します。
各プロセスには複数のスレッドが含まれており、プロセス内の複数のスレッドも連携してプロセスの責任を果たします。
Chromeはマルチプロセスアーキテクチャを採用しており、最上位層にブラウザプロセスがあり、ブラウザの他のプロセスを調整します。
利点
新しいタブページがデフォルトで開かれ、新しいプロセスが作成されるため、単一のタブページのクラッシュはブラウザ全体に影響しません。
同様に、サードパーティのプラグインのクラッシュはブラウザ全体には影響しません。
マルチプロセスは、最新のCPUマルチコアの利点を最大限に活用できます。
サンドボックスモデルを使用してプラグインなどのプロセスを分離し、ブラウザの安定性を向上させると便利です。
不利益
システムは、ブラウザの新しく開かれたプロセスにメモリ、CPU、およびその他のリソースを割り当てるため、メモリとCPUのリソース消費が大きくなります。
ただし、Chromeはメモリの解放に優れており、基本的なメモリをすばやく解放して、他のプログラムを実行できます。
ブラウザの主なプロセスと責任
主进程Browser Process
ブラウザインターフェースの表示と対話を担当します。各ページの管理、他のプロセスの作成と破棄。ネットワークリソースの管理、ダウンロードなど
第三方插件进程 Plugin Process
プラグインの各タイプはプロセスに対応し、プラグインが使用された場合にのみ作成されます。
GPU 进程 GPU Process
3D描画などに使用される高々1つしかありません。
渲染进程 Renderer Process
これは、ブラウザレンダリングプロセスまたはブラウザカーネルと呼ばれ、内部的にマルチスレッド化されています。主にページのレンダリング、スクリプトの実行、イベント処理などを担当します。(この記事は分析に焦点を当てています)
渲染进程 Renderer Process
ブラウザのレンダリングプロセスはマルチスレッド化されています。ブラウザのメインスレッドを見てみましょう。
1つのGUIレンダリングスレッド
-
ブラウザインターフェースのレンダリング、HTML、CSSの解析、DOMツリーとRenderObjectツリーの構築、レイアウトと描画などを担当します。
-
なんらかの操作が原因でインターフェイスを再描画またはリフローする必要がある場合、スレッドが実行されます。
-
GUIレンダリングスレッドとJSエンジンスレッドは相互に排他的であることに注意してください。JSエンジンが実行されると、GUIスレッドは中断され(凍結と同じ)、JSエンジンがアイドル状態になるまでGUI更新がキューに保存されます。 。
2 JSエンジンスレッド
- JSカーネルとも呼ばれるJavaScriptエンジンは、JavaScriptスクリプトプログラムの処理を担当します。(例:V8エンジン)
- JSエンジンスレッドは、JavaScriptスクリプトの解析とコードの実行を担当します。
- JSエンジンは、タスクキューにタスクが到着するのを待ってから処理しています。タブページ(レンダラープロセス)でJSプログラムを実行しているJSスレッドは常に1つだけです。
- GUIレンダリングスレッドとJSエンジンスレッドは相互に排他的であるため、JS実行時間が長すぎると、ページのレンダリングに一貫性がなくなり、ページのレンダリングがブロックされます。
3イベントトリガースレッド
- イベントループの制御に使用されるJSエンジンではなくブラウザに属します(当然、JSエンジン自体がビジー状態であり、ブラウザがスレッドを開いて支援する必要があります)
- JSエンジンがsetTimeOutなどのコードブロックを実行すると(マウスクリック、AJAX非同期要求など、ブラウザーカーネルの他のスレッドからも)、対応するタスクがイベントスレッドに追加されます。
- 対応するイベントがトリガー条件を満たし、トリガーされると、スレッドは処理するキューの最後にイベントを追加し、JSエンジンが処理するのを待ちます
- JSのシングルスレッド関係により、これらの保留中のキュー内のイベントは、JSエンジンによる処理のためにキューに入れられる必要があることに注意してください(JSエンジンがアイドル状態のときにのみ実行されます)
4.タイミングトリガースレッド
- 伝説的なsetIntervalおよびsetTimeoutが配置されているスレッド
- ブラウザのタイマーカウンターはJavaScriptエンジンではカウントされません(JavaScriptエンジンはシングルスレッドなので、ブロックされたスレッド状態にあると、タイミングの精度に影響します)。
- したがって、別のスレッドを使用してタイミングを計時し、トリガーします(タイミングが完了した後、それはイベントキューに追加され、JSエンジンがアイドル状態になった後に実行されます)。
- W3Cは、HTML標準でsetTimeoutの4ミリ秒未満の時間間隔は4ミリ秒としてカウントされることを規定していることに注意してください。
5非同期http要求スレッド
- XMLHttpRequestが接続された後、新しいスレッド要求がブラウザーを介して開かれます。
- 状態変更が検出されると、コールバック関数が設定されている場合、非同期スレッドは状態変更イベントを生成し、このコールバックをイベントキューに入れます。次に、JavaScriptエンジンによって実行されます。
6補足:Webワーカー
Webワーカーは、JavaScriptがマルチスレッドを可能にする方法です。これは別のコンピューティングスレッドです。詳細については、Ruan Dashenのログを参照してください。
概要:通常使用するjsは、ブラウザープロセスの単なるスレッドです。多くの非同期操作、イベントコールバック管理などは追加のスレッドで処理する必要があり、JavaScriptスレッドはイベントの形で通知されます。
ブラウザのレンダリングプロセス
URLの入力からページの読み込みまで何が起こるかについて話したい場合、それは無限です...ここでは、ブラウザのレンダリングプロセスについてのみ説明します。
-
HTMLファイルを解析し、DOMツリーを構築し、メインのブラウザプロセスがCSSファイルのダウンロードを担当します。
-
CSSファイルがダウンロードされ、CSSファイルがツリー状のデータ構造に解析され、DOMツリーと結合されて、RenderObjectツリーが形成されます。
-
RenderObjectツリーのレイアウト(レイアウト/リフロー)。RenderObjectツリーの要素のサイズと位置の計算を担当します。
-
RenderObjectツリー(ペイント)を描画し、ページのピクセル情報を描画します
-
メインのブラウザープロセスは、デフォルトレイヤーと複合レイヤーをGPUプロセスに渡し、GPUプロセスは各レイヤー(複合)を複合し、最後にページを表示します
回答
1.为什么 Javascript 要是单线程的 ?
これは、スクリプト言語JavaScriptの使命によるものです!JavaScriptは、ページでのユーザー操作を処理し、DOMツリーとCSSスタイルツリーを操作して、動的でリッチなインタラクティブエクスペリエンスとサーバーロジックのインタラクティブ処理をユーザーに提供します。
JavaScriptがこれらのUI DOMを操作するマルチスレッドの方法である場合、UI操作で競合が発生する可能性があります。
JavaScriptがマルチスレッドである場合、マルチスレッドの相互作用の下で、UIのDOMノードが重要なリソースになる可能性があります。
DOMを同時に操作する2つのスレッドがあり、一方が変更を担当し、もう一方が削除を担当すると仮定すると、ブラウザーは、どのスレッドの実行結果を有効にするかを決定する必要があります。
もちろん、ロックすることで上記の問題を解決できます。しかし、ロックの導入によって引き起こされる複雑さを回避するために、JavaScriptは最初にシングルスレッド実行を選択しました。
2. 为什么 JS 阻塞页面加载 ?
JavaScriptはDOMを操作できるため、インターフェースのレンダリング中にこれらの要素属性を変更すると(つまり、JavaScriptスレッドとUIスレッドが同時に実行されます)、レンダリングスレッドの前後で取得された要素データに一貫性がなくなる可能性があります。
したがって、予測できない結果がレンダリングされないようにするために、ブラウザーの設定GUI 渲染线程与 JavaScript 引擎为互斥的关系。
当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
上記から、GUIレンダリングスレッドとJavaScript実行スレッドの間には相互に排他的な関係があるため、
ブラウザがJavaScriptプログラムを実行しているとき、GUIレンダリングスレッドはキューに格納され、JSプログラムが実行されるまで続行されません。
したがって、JS実行時間が長すぎると、ページのレンダリングに一貫性がなくなり、ページのレンダリングとロードがブロックされているように感じられます。
3. css 加载会造成阻塞吗 ?
上記のブラウザーレンダリングプロセスから、次のことがわかります。
DOM解析とCSSの構文解析はそう、2つの並列処理されていますCSS 加载不会阻塞 DOM 的解析
。
ただし、Render TreeはDOMツリーとCSSOMツリーに依存しているため、
したがって、レンダリングする前に、CSSOMツリーが構築される、つまりCSSリソースがロードされる(またはCSSリソースがロードに失敗する)まで待つ必要があります。
したがって、CSS 加载会阻塞 Dom 的渲染。
JavaScriptはDOMスタイルとCSSスタイルを操作できるため、インターフェースのレンダリング中にこれらの要素属性を変更すると(つまり、JavaScriptスレッドとUIスレッドが同時に実行されます)、レンダリングスレッドの前後で取得された要素データに一貫性がなくなる可能性があります。
したがって、予測できない結果がレンダリングされないようにするために、ブラウザーの設定 GUI 渲染线程与 JavaScript 引擎为互斥的关系。
したがって、スタイルシートは、後続のjsが実行される前に読み込まれて実行されるため、css 会阻塞后面 js 的执行。
4 DOMContentLoaded 与 load 的区别 ?
-
DOMContentLoadedイベントがトリガーされると、DOM解析が完了した後にのみ、スタイルシートと画像は含まれません。CSSの読み込みはDomのレンダリングとjsの実行を後でブロックし、jsはDomの解析をブロックすることを前述したので
、ドキュメントにスクリプトがない場合、ブラウザーはドキュメントの解析後にDOMContentLoadedイベントをトリガーできると結論付けることができます。ドキュメントにスクリプトが含まれている場合、スクリプトはドキュメントの解析をブロックするため、CSSOMのビルド後にスクリプトを実行する必要があります。いずれの場合でも、DOMContentLoadedのトリガーは、画像やその他のリソースが読み込まれるのを待つ必要はありません。 -
onloadイベントがトリガーされると、ページ上のすべてのDOM、スタイルシート、スクリプト、画像、その他のリソースが読み込まれます。
-
DOMContentLoaded-> load。
5. 什么是 CRP,即关键渲染路径(Critical Rendering Path)? 如何优化 ?
クリティカルレンダリングパスは、ブラウザーがHTML CSS JavaScriptを画面にレンダリングされるピクセルコンテンツに変換するために実行する一連のステップです。これは、前述のブラウザレンダリングプロセスです。
最初のレンダリングをできるだけ早く完了するために、次の3つの変数を最小化する必要があります。
-
重要なリソースの数:Webページの最初のレンダリングを妨げる可能性があるリソース。
-
クリティカルパスの長さ:すべてのクリティカルリソースを取得するために必要なラウンドトリップ数または合計時間。
-
キーワードセクション:Webページの最初のレンダリングに必要な合計バイト数は、すべての主要リソースのファイルサイズの合計に相当します。
1. DOMを最適化する
- スペースを含む不要なコードとコメントを削除し、ファイルを最小化してください。
- GZIPを使用してファイルを圧縮できます。
- HTTPキャッシュファイルと組み合わせる。
2. CSSOMを最適化する
CSSOMの場合、ページのレンダリングが妨げられることを前述したので、この点から最適化を検討できます。
- 主要なCSS要素の数を減らす
- スタイルシートを宣言するときは、メディアクエリの種類に細心の注意を払ってください。CRPのパフォーマンスに大きく影響します。
3. JavaScriptを最適化する
ブラウザがスクリプトタグに遭遇すると、パーサーは動作し続けることができなくなります。CSSOMが構築されるまで、JavaScriptが実行され、DOM構築プロセスを完了し続けます。
-
async:async属性をスクリプトタグに追加した後、ブラウザーはこのスクリプトタグを検出したときにDOMの解析を続行し、スクリプトはCSSOMによってブロックされません。つまり、CRPはブロックされません。
-
延期:asyncとの違いは、ドキュメントが解析された後(DOMContentLoadedイベントの前)にスクリプトを実行する必要があることです。一方、非同期では、ドキュメントの解析中にスクリプトをバックグラウンドで実行できます(2つのダウンロードプロセスはDOMをブロックしませんが、実行はブロックされます)。
-
スクリプトがDOMまたはCSSOMを変更しない場合は、非同期を使用することをお勧めします。
-
プリロード-プリロードおよびプリフェッチ。
-
DNS pre-resolution-dns-prefetch。
6. defer 和 async 的区别 ?
ブラウザがスクリプトスクリプトに遭遇すると、次のようになります。
-
<script src="script.js">
遅延または非同期がない場合、ブラウザーは指定されたスクリプトを即座にロードして実行します。「即時」は、レンダリング前にスクリプトタグの下のドキュメント要素を参照します。つまり、次のロードされたドキュメント要素を待たずに、読み込まれたときにそれをロードします。そして実行します。 -
<script async src="script.js">
asyncを使用すると、後続のドキュメント要素の読み込みとレンダリングのプロセスは、script.jsの読み込みと実行(非同期)と並行になります。
<script defer src="myscript.js">
deferを使用すると、後続のドキュメント要素の読み込みプロセスは、script.jsの読み込みと並行して(非同期で)実行されますが、script.jsの実行は、すべての要素が解析された後、DOMContentLoadedイベントがトリガーされる前に完了する必要があります。
これは古いブラウザーの唯一の最適化の選択肢であるため、実際には、すべてのスクリプトを前にスローすることがベストプラクティスです。この方法により、スクリプトではない他のすべての要素を最速で取得できます。読み込みと解析。
次に、写真を見てみましょう:
青い線はネットワークの読み取りを表し、赤い線は実行時間を表します。どちらもスクリプト用です。緑の線はHTML解析を表します。
したがって、結論付けることができます。
-
Deferとasyncはネットワーク読み取り(ダウンロード)で同じですが、どちらも非同期です(HTML解析と比較して)。
-
2つの違いは、スクリプトがダウンロードされた後にスクリプトが実行されるときです。明らかに、遅延は、アプリケーションスクリプトのロードと実行に関する要件に最も近いものです。
-
延期に関して、この図の未完成の部分は、ロード順にスクリプトを実行することであり、賢く使用する必要があります
-
asyncはアウトオブオーダー実行のマスターです。とにかく、スクリプトのロードと実行は互いに隣り合っているため、宣言した順序に関係なく、ロードされている限りすぐに実行されます。
-
慎重に考えてください。非同期は依存関係をまったく考慮しないため(最低次の実行であっても)、アプリケーションスクリプトにはあまり役立ちませんが、スクリプトに依存していないスクリプトには依存していません。非常に適切
7.谈谈浏览器的回流与重绘
回流必将引起重绘,重绘不一定会引起回流。
リフロー
Render Treeの一部またはすべての要素のサイズ、構造、または一部のプロパティが変更されると、ドキュメントの一部またはすべてをブラウザが再レンダリングするプロセスは、リフローと呼ばれます。
逆流を引き起こす操作:
ページの最初のレンダリング
ブラウザウィンドウのサイズが変更されました
要素のサイズまたは位置が変更されます。要素の内容が変更されます(テキストや画像のサイズなど)。
要素のフォントサイズの変更
表示されるDOM要素を追加または削除する
CSS疑似クラスをアクティブ化します(例::hover)
特定のプロパティのクエリまたは特定のメソッドの呼び出し
リフローを引き起こす一般的に使用されるいくつかのプロパティとメソッド:
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
塗り直し
ページ上の要素スタイルの変更がドキュメントフロー内の位置に影響しない場合(例:色、背景色、可視性など)、ブラウザは新しいスタイルを要素に割り当てて再描画します。このプロセスは再描画と呼ばれます。描きました。
パフォーマンスへの影響
回流比重绘的代价要更高。
場合によっては、1つの要素だけがリフローされても、その親要素とそれに続くすべての要素がリフローされます。最新のブラウザーは、頻繁なリフローまたは再描画操作を最適化します。ブラウザーはキューを維持し、リフローと再描画の原因となるすべての操作をキューに入れます。キュー内のタスク数または時間間隔がしきい値に達した場合、ブラウザはキューを空にして、複数のリフローと再描画を1つに変えることができるバッチプロセスを実行します。
次のプロパティまたはメソッドにアクセスすると、ブラウザはすぐにキューをクリアします。
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
width、height
getComputedStyle()
getBoundingClientRect()
これらのプロパティまたはメソッドの戻り値に影響を与える操作がキューにある可能性があるため、取得したい情報がキュー内の操作によって引き起こされた変更に関連していない場合でも、ブラウザーは強制的にキューを空にして、取得した値が最も正確であることを確認します。
回避する方法
CSS
-
テーブルレイアウトの使用は避けてください。
-
DOMツリーの最後のクラスをできるだけ変更します。
-
複数のインラインスタイルを設定しないでください。
-
位置属性が絶対または固定の要素にアニメーション効果を適用します。
-
CSS式の使用は避けてください(例:calc())。
Javascript
-
スタイルの頻繁な操作を回避するには、スタイル属性を1回書き換えるか、スタイルリストをクラスとして定義し、クラス属性を1回変更することをお勧めします。
-
DOMの頻繁な操作を避け、documentFragmentを作成し、それにすべてのDOM操作を適用して、最後にドキュメントに追加します。
-
display:noneを最初に要素に設定し、操作の終了後に表示することもできます。なぜなら、表示属性がnoneである要素に対してDOM操作を実行しても、リフローと再描画は発生しないからです。
-
リフロー/再描画を引き起こす属性を頻繁に読み取ることは避けてください。本当に複数回使用する必要がある場合は、変数を使用してそれらをキャッシュしてください。
-
複雑なアニメーションを持つ要素の絶対配置を使用して、要素をドキュメントフローの外に出します。そうしないと、親要素と後続の要素が頻繁にリフローされます。