フロントエンドのパフォーマンス指標の説明については、業界によって独自の意見があります。要約すると、最初の画面のパフォーマンスとページの流暢性に関するものです。ページの流暢性の分析の観点。 [関連チュートリアルの推奨:「Angular チュートリアル」]
ページの流暢性とは何ですか?
ページの流暢さはフレーム レート FPS (Frames Per Second - 1 秒あたりに送信されるフレーム数) によって決まります。一般に、主流のブラウザの画面リフレッシュ レートは 60 Hz (1 秒あたり 60 回リフレッシュされる) であり、最適なフレーム レートは 60 FPS です。フレーム レートが 60 Hz であれば、表示画面が 16.6 ミリ秒ごとに更新されることを意味します。つまり、各ページのレンダリングは 16.6 ミリ秒以内に完了する必要があり、そうしないとページがフレームを失ってフリーズします。根因在于:浏览器中的JavaScript 执行和页面渲染会相互阻塞
。
次の図に示すように、Chrome の開発ツールでは、Cmd+Shift+P を実行して show fps と入力すると、fps パネルをすぐに開くことができます。
FPS パネルを観察することで、現在のページの流暢さを簡単に監視できます。
1 ページのパフォーマンスに影響を与える要因
ページのインタラクションがスムーズかどうかは、ページの応答がスムーズかどうかによって決まります。ページの応答は基本的に、ページのステータスの変化をページに再レンダリングするプロセスです。
ページの応答プロセスは大まかに次のとおりです。
一般に、イベント ハンドラーのイベント処理ロジックはそれほど多くの時間を消費しないため、Angular のパフォーマンスに影響を与える要因は主に、异步事件触发
と变更检测
にあります。 一般に、イベント ハンドラーのイベント処理ロジックはそれほど多くの時間を消費しないため、Angular のパフォーマンスに影響を与える要因は主に、非同期イベントのトリガーと変更検出にあります。
Angular の場合、ページ レンダリングのプロセスは変更検出のプロセスであり、ページ フレームの損失や遅延を避けるために、Angular の変更検出は 16.6 ミリ秒以内に完了する必要があることがわかります。
ページ応答のパフォーマンスは、次の 3 つの側面から最適化できます。
(1) トリガー イベント ステージの場合、非同期イベントのトリガーを減らして、変更検出と再レンダリングの全体的な数を減らすことができます。
(2) イベント ハンドラーの実行ロジック ステージの場合、複雑な処理を最適化することで実行時間を短縮できます。コード ロジック。
(3) 変更検出データ バインディングと DOM 更新フェーズでは、変更検出とテンプレート データの計算数を削減して、
(2) イベント ハンドラーの特定の問題を分析する必要があります。詳細については説明しないため、主に (1) (3) に焦点を当てます。 ) 最適化
2最適化計画
2.1 Angular の非同期イベントのトリガーを減らす
デフォルトの変更検出モードでは、非同期イベントがグローバルな変更検出をトリガーします。 Angular のパフォーマンスが大幅に向上します。
非同期イベントには、マクロ タスク イベントとマイクロ タスク イベントが含まれます
非同期イベントの最適化は、主にドキュメント リスニング イベントに対して行われます。たとえば、ドキュメント上のクリック、マウスアップ、マウス移動などのイベントをリッスンします。
リスニング イベント シナリオ:
Renderer2.listen(document,…)
fromEvent(document,…)
document.addEventListener(…)
DOM リスニング イベントは、トリガーする必要がない場合には削除する必要があります。
例: [pop] プロンプト ボックス コマンドの
使用シナリオ: テーブル列のフィルタリング、アイコン以外の場所のクリック、またはページのスクロール、列フィルター ポップアップ ボックスの非表示
以前の方法では、クリック イベントとスクロール イベントを直接監視していました。唯一の欠点は、プロンプト ボックスが表示されないことですが、それでも監視イベントが存在するため、これは非常に不合理です。
合理的な解決策: プロンプト ボックスが表示されている場合のみクリック イベントとスクロール イベントをリッスンし、プロンプト ボックスが非表示になっている場合はリッスン イベントを削除します。
頻繁にトリガーされる DOM リスニング イベントの場合、rjx 演算子を使用してイベントを最適化できます。詳細については、「Rjx 演算子」を参照してください。 RxJS ビー玉。
2.2 変化検出
変化検出とは何ですか?
変更検出を理解するには、変更検出の目標から答えを探すことができます。角度変化検出の目的は、モデル (TypeScript コード) とテンプレート (HTML) の同期を維持することです。したがって、変更の検出は、モデルの変更を検出し、同時にテンプレート ( DOM )を更新するものとして理解できます。
変化検出プロセスとは何ですか?
コンポーネント ツリー内でトップダウンの順序で変更検出を実行することにより、つまり、最初に親コンポーネントに対して変更検出が実行され、次に子コンポーネントに対して変更検出が実行されます。まず親コンポーネントのデータ変更を確認し、次に親コンポーネントのテンプレートを更新します。テンプレートを更新するときに、子コンポーネントが見つかると、子コンポーネントにバインドされている値が更新され、子コンポーネントに入って、 @Input 入力値のインデックスが変更された場合は、サブコンポーネントをダーティとしてマークします。これには、サブコンポーネントをマークした後、すべての親コンポーネント テンプレートの後でテンプレートを更新します。が更新されている場合は、サブコンポーネントの検出を変更します。
2.2.1 Angular の変更検出の原理
デフォルトの変更検出デフォルト モードでは、Angular の変更検出をトリガーする非同期イベントの原理は、ルート コンポーネントから実行する Zone.js を使用して非同期イベントを処理するときに、Angular が ApplicationRef の Nick() メソッドを呼び出すことです。サブコンポーネントへの変更検出。 Angular アプリケーションの初期化プロセス中に、ゾーン (NgZone) がインスタンス化され、すべてのロジックがオブジェクトの _inner オブジェクト内で実行されます。
Zone.js は次のクラスを実装します。
検出プロセスの原理は大まかに次のとおりです。
ユーザー操作により非同期イベントがトリガーされます (例: DOM イベント、インターフェイス リクエストなど)
=> ZoneTask クラスがイベントを処理します。ゾーンの runTask() メソッドは invokeTask() 関数で呼び出されます。 runTask メソッドでは、zone は _zoneDelegate インスタンス属性を渡し、ZoneSpec のフックを呼び出します
=> ZoneSpec の 3 つのフック (onInvokeTask、onInvoke、onHasTask) が checkStable を渡します。 () function トリガーzone.onMicrotaskEmpty.emit(null) notification
=> ルートコンポーネントは、onMicrotaskEmptyをリッスンした後、tick()を呼び出し、tickメソッド内でdetectChanges()
を呼び出し、ルートコンポーネントから検出を開始します
=> ··· refreshView()
、 executeTemplate
メソッド内で、 executeTemplate()
を呼び出します。 templateFn()
を呼び出して、テンプレートとサブコンポーネントにバインドされた値を更新します (この時点で、输入引用是否改变,如果有改变,会将子组件标记为
这时候会去检测子组件的
。输入引用是否改变,如果有改变,会将子组件标记为
,也就是该子组件需要变更检测
変更検出原理の詳細なフローチャート:
プロセスを簡略化します:
非同期イベントをトリガー
=> ZoneTask がイベントを処理
=> ZoneDelegate が ZoneSpec フックを呼び出して onMicrotaskEmpty 通知をトリガー
=> ルート コンポーネントが onMicrotaskEmpty 通知を受信し、tick() を実行し、dom の検出と更新を開始します
上記のコードからわかるように、当微任务为空的时候才会触发变更检测
。
単純な変化検出原理フローチャート:
Angular ソース コード分析 Zone.js リファレンス ブログ。
2.2.2 変更検出の最適化ソリューション
1) OnPush モードの
原則を使用する: 1 つの変更検出にかかる時間を削減します。
OnPush モードと Default モードの違いは、DOM リスニング イベント、タイマー イベント、Promise が変更検出をトリガーしないことです。デフォルト モードのコンポーネントのステータスは常に CheckAlways です。これは、コンポーネントが検出サイクルごとにテストされる必要があることを意味します。
OnPush モード: 次の状況では、変更検出
S1 がトリガーされ、コンポーネントの @Input 参照が変更されます。
S2. コンポーネントの DOM にバインドされたイベント (クリック、送信、マウス ダウンなど、そのサブコンポーネントの DOM にバインドされたイベントを含む)。 @HostListener()
注:
renderer2.listen() を通じて監視される DOM イベントは、変更検出をトリガーしません。dom.addEventListener
() のネイティブ リスニング メソッドは、
S3 および Observable サブスクリプション イベントも設定されます。同時に。
S4. 次のメソッドを使用して、変更検出を手動でトリガーします:
ChangeDetectorRef.detectChanges(): 現在のコンポーネントおよび非 OnPush サブコンポーネントの変更検出をトリガーします。
ChangeDetectorRef.markForCheck(): 現在のビューとそのすべての祖先をダーティとしてマークすると、次の検出サイクルで検出がトリガーされます。
ApplicationRef.tick(): 変更検出をトリガーしません。
2)
NgZone.runOutsideAngular() を使用する原則: 変更検出の数を減らし
、NgZone.runOutsideAngular() メソッドのコールバックにグローバル dom イベント監視を記述します。角度変化検出をトリガーしません。現在のコンポーネントが更新されていない場合は、コールバック関数で ChangeDetectorRef の detectChanges() フックを実行して、現在のコンポーネントの変更検出を手動でトリガーできます。
例: app-icon-react 動的アイコン コンポーネント
2.2.3 デバッグ方法
1: ブラウザ コンソールで Angular DevTools プラグインを使用して、特定の DOM イベントと Angular の検出ステータスを表示できます。
方法 2: コンソールに ng.profiler.timeChangeDetection() と直接入力して、検出時間を表示できます。このメソッドでは、グローバル検出時間を表示できます。参考ブログ プロファイリング角度変化検出
2.3 テンプレート (HTML) の最適化
2.3.1 DOM レンダリングの削減: ngFor と trackBy
*ngFor の trackBy 属性を使用すると、Angular はエントリ リスト全体を再ロードすることなく、変更されたエントリのみを変更し、再レンダリングします。
例: テーブルの並べ替えシナリオ。 trackBy を ngFor に追加すると、テーブルのレンダリング時に行 dom 要素のみが移動します。trackBy を追加しない場合は、最初に既存のテーブル dom 要素が削除され、次に行 dom 要素が追加されます。明らかに、dom 要素のみを移動する場合のパフォーマンスは大幅に向上します。
2.3.2 テンプレート式で関数を使用しないでください。
Angular テンプレート式で関数呼び出しを使用しないでください。代わりにパイプを使用することも、手動計算後に変数を使用することもできます。テンプレート内で関数を使用している場合、値が変更されたかどうかに関係なく、変更検出が行われるたびに関数が実行されるため、パフォーマンスに影響します。
テンプレートで関数を使用するシナリオ:
2.3.3 ngFor の使用を減らす
データ量が多い場合、ngFor を使用するとパフォーマンスに影響します。
例:
ngFor の使用:
ngFor を使用しない場合: パフォーマンスが約 10 倍向上