角度1深さ解析:ダーティデータチェックと角度性能最適化

TL; DR

  1. ダーティデータチェックは、によってモデル・ビュー・マッピングメカニズムである$applyまたは$digestトリガー。
  2. ダーティチェックの範囲は、領域またはコンポーネントパーティションに関係なく、ページ全体です
  3. 可能な限り単純なバインディング式で汚れチェック実行速度を向上させる
  4. (ページ上の単一の結合および結合式の数減らすようにしてくださいng-if
  5. するにはng-repeat追加track by作り、角度再利用する既存の要素

ダーティチェックとは何ですか?

Angularは、双方向データバインディングを提供するMVVMフロントエンドフレームワークです。 いわゆる双方向データ・バインディング(双方向データ・バインディング)は、ページ要素の変更がViewモデルの対応するデータ変更をトリガーし、Viewモデルのデータ変更が更新されたUI要素データ更新をトリガーするということです。 操作データは、UIの操作と同じです。

一見単純ですが、実際には水は非常に深いです。 UI要素の変更は、(例えば、対応するDOMイベント結合によってモデルデータの変更につながるinputまたはchange達成するために簡単であることができる);しかし、今度は、それほど容易ではありません。

たとえば、次のコードがあります。

<p ng-bind="content1"></p>
<p ng-bind="content2"></p>
<button ng-click="onClick()">Click Me</button>

ユーザーがボタンをクリックし、名前の男実行の角度onClickメソッドを。 onClick角度の体のための方法は、最終的には知りませんでしたブラックボックスです。 変更される可能性があり$scope.content1値を、変更することがあり$scope.content2二つの値が変更されている可能性、価値を、それは決して変わらないことがあります。

だから、最後に、私たちは知っている、角べきかonClick() DOM要素を更新する必要があるUIは、このコードの後にリフレッシュする必要があるかどうか、?

Angularは、これらの要素のバインディング式の値が変更されているかどうかを確認する必要があります。 これがダーティデータチェックの起点です(ダーティデータチェックをダーティチェックと呼びます)。

ダーティチェックがどのようにトリガされるか

UIの変更が発生した場合、Angularは汚れたチェックを行います。この文は正確ではありません。 実際、審査が汚れている] $ダイジェスト(https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest)[別の、より一般的に使用される機能が汚れチェックをトリガに実行$適用実際には$digestシンプルなパッケージを(も行っていくつかの作業が例外をキャッチ)。

通常時に我々は呼び出すためのコードを記述するためのイニシアチブを取ることはありません$applyまたは$digest角度のため、梱包を行うために私たちのコールバック関数の外側に。 このような従来のようにng-clickコマンド(ある、 Directive )、内部実装はそれています

DOM.addEventListener('click', function ($scope) {
  $scope.$apply(() => userCode());
});

私たちは見ることができます: ng-click私たちが行う支援するために$applyこの操作を。 これらは、ちょうど同様のイベントのコールバック関数と同様ではありません$http 、$ $timeoutようにと。 私は限り、あなたは手動で呼び出すことを覚えているように、これらのサービス(サービス)が付属しています実際には、あなたがそれを使用することはできませんが、多くの人々は、このライブラリが大きすぎる角パイプの何でもあると文句を言う聞い$scope.$apply

汚れ検査範囲

前述したように、angleはUIにバインドされたすべての式の汚れチェックを行います。 実際には、内部の角度の実現に、すべての結合式がに変換されます$scope.$watch()$watch 、式の最後の値を記録します。 そこng-bind="a"つまり$scope.$watch('a', callback) 、および$scope.$watchコントロールすることはできませんwatchでイベントトリガダーティチェックと表現するかどうか。

例えば:

<div ng-show="false">
  <span id="span1" ng-bind="content"></span>
</div>
<span id="span2" ng-bind="content"></span>
<button ng-click="">TEST</button>

Q:クリックTESTこのボタンを行う際に汚れチェックをトリガーしますか? 数回トリガー?

まず: ng-click=""は何の関係もありません。 このイベントコールバック関数は何もしないので、angleはダーティチェックを実行しませんか? いいえ。

次に: #span1それが上記の式にバインドを確認し、隠されていますか? ユーザーが見ることができないが、しかし、 $scope.$watch('content', callback) 」、コールバック)がまだそこにあります。 あなただけのこの入れた場合でもspanウォッチ式がまだチェックされますチェックされる限り、取り除くために要素を。

繰り返される式は繰り返し確認されますか? ウィル

最後に:忘れてはいけないng-show="false" 。 おそらくので、角度の開発者は、この場合は、結合定数はまれで、そう思う$watch 、監視式が一定であるかどうかを識別しませんでした。 定数は繰り返しチェックされます。

そう:

A:トリガを3回繰り返します。 falsecontentcontent

したがって、バインディング式は、現在のDOMツリーに表示されているかどうかにかかわらず、表示されているかどうかにかかわらず、ユーザーの操作に関係なく、別のタブに配置されているかどうかに関係なく監視されます。

また、さらには異なるでController構造の$scopeも、角だけでなく、グローバル忘れてはいけない、お互いに影響を及ぼします$rootScope 、あなたはまだできる$scope.$emit 。 あなたは決してにすることを保証することはできません、角度controllerに別のものに変更しcontroller生成scopeで生成されたカスタム指令(指令)を含め、 scopeAngular 1.5新たに導入されたのコンポーネント(コンポーネント)。

フォームが入力されたときに、ページの左側にあるナビゲーションバーの変更をユーザーが聞くかどうかは疑いありません。

汚れ検査と作業効率

汚れたチェックは遅いですか?

正直言って、汚れた検査の効率は高くありませんが、それは遅くはありません。 シンプルな数字や文字列はどれくらい遅いのですか? ダースの式の汚れたチェックは直接無視することができますが、何百ものものを受け入れることができ、何十万も大きな問題があります。 多数の式を束縛するときの結合式の効率に注意してください。 次の点に注意することをお勧めします。

  1. 式で書かれたあまり複雑でないロジック(および式によって呼び出される関数)
  2. あまりにも長い間接続しないでくださいfilter (多くの場合、フィルタに横断し、新しい配列を生成します)
  3. DOM要素にはアクセスしないでください。

単一のバインドでバインディング式の数を減らす

単一の結合( ワンタイムバインディングは角度1.3の導入の式の特別な一種である、それは::汚れ検査は、式の値がされていないことを明らかに始まり、 undefined 、この式は安定していると考えられそしてこの式の監視を解除してください。バインディング式の数、および削減する効果的な方法であるng-repeatより良い効果後(それは後述します)が、過度の使用が簡単にバグにつながることができます。

作りの良い使用ng-if結合式の数を減らします

あなたはと思うならNG-場合それはあなたが間違っている、別の表示方法DOM要素を隠すために使用されます。

ng-ifあなただけ(というようなDOMツリー内の要素の数減らすことはできませんng-hideこれだけの追加display: none )、各ng-if 、独自scopeng-if 、以下の$watch式があります登録されたng-if 、独自scopeインチ 場合はng-ifなっfalseng-ifの下のscopeに登録さ、破壊されたscopeそれに伴いバインディング式が破壊されました。

このタブの実装について考えてみましょう。

<ul>
  <li ng-class="{ selected: selectedTab === 1 }">Tab 1 title</li>
  <li ng-class="{ selected: selectedTab === 1 }">Tab 2 title</li>
  <li ng-class="{ selected: selectedTab === 1 }">Tab 3 title</li>
  <li ng-class="{ selected: selectedTab === 1 }">Tab 4 title</li>
</ul>
<div ng-show="selectedTab === 1">[[Tab 1 body...]]</div>
<div ng-show="selectedTab === 2">[[Tab 2 body...]]</div>
<div ng-show="selectedTab === 3">[[Tab 3 body...]]</div>
<div ng-show="selectedTab === 4">[[Tab 4 body...]]</div>

これは、表示された隠し要素に対して繰り返される、最初の反応は、人々が使う通常、 ng-showng-hide簡単に使用display: none要素には、目に見えないに設定されていません。

しかし、上述したように、肉眼では見えないということは、それが汚れていないことを意味するものではありません。 場合はng-show置き換えng-ifng-switch-when

<div ng-if="selectedTab === 1">[[Tab 1 body...]]</div>
<div ng-if="selectedTab === 2">[[Tab 2 body...]]</div>
<div ng-if="selectedTab === 3">[[Tab 3 body...]]</div>
<div ng-if="selectedTab === 4">[[Tab 4 body...]]</div>

次の利点があります。

  1. まず、DOMツリーの要素数を4分の1に大幅に削減し、メモリ使用量を削減します。
  2. 第二に、 $watch式はまた1/4に低減することができ、ダーティチェックのスピードの循環を高めるために、
  3. 以下タブ場合controller場合(例えば、各タブは、構成要素としてカプセル化されている)は、タブが選択されているのみときcontroller実装され、ページの相互干渉を低減することができます
  4. 場合はcontrollerコール・インタフェースのデータを取得し、それに対応する場合にのみ、 tab選択したネットワークの混雑を避けるためにロードされます

もちろん、欠点もあります。

  1. DOMの再構築には時間がかかります
  2. 次のタブの場合controllerは、タブが選択されるたびにcontroller実行されます
  3. 場合controller調整インターフェースは、内部のデータを得るために、次にタブが選択されるたびに再ロードされます

読者は自分自身を選ぶ。

汚れたチェックが配列に遭遇すると

ng-repeat ! これはもっと興味深いです(xin)。 典型的に(結合オブジェクトが部材の内部オブジェクトにバインドされている)の値の変化をリッスンするためにのみ結合し、 ng-repeatオブジェクトの全体の配列の変化を監視しなければなりません。 例えば:

<ul ng-init="array = [
  { value: 1 },
  { value: 2 },
  { value: 3 },
  { value: 4 },
]">
  <li ng-repeat="item in array" ng-bind="item.value"></li>
</ul>

これは、4つの生成li要素を

  • 1
  • 2
  • 3
  • 4

問題ありません。 このようなボタンを追加すると:

<button ng-click="array.shift()">删除第一个元素</button>

検討してください:ユーザーがこのボタンをクリックするとどうなりますか?

私たちは段階的に分析します。 最初に、angularは配列の初期状態を次のように記録します。

[
  { "value": 1 },
  { "value": 2 },
  { "value": 3 },
  { "value": 4 }
]

ユーザーがボタンをクリックすると、配列の最初の要素が削除され、配列は次のようになります。

[
  { "value": 2 },
  { "value": 3 },
  { "value": 4 }
]

2つの間の比較:

  1. array.length = 4 => array.length = 3
  2. array[0].value = 1 => array[0].value = 2
  3. array[1].value = 2 => array[1].value = 3
  4. array[2].value = 3 => array[2].value = 4
  5. array[3].value = 4 => array[3].value = undefinedarray[4]undefinedundefined.value未定義で、参照説明角表現

あなたが見ることができるように:角度比較後、あなたが表示されます:

  1. 配列の長さを1減らした
  2. 配列の最初の要素の値が2に変更されます。
  3. 配列の2番目の要素の値が3に変更されます。
  4. 配列の3番目の要素の値が4に変更されます。

DOM要素への応答は次のとおりです。

  1. 最初の1個li 2へのコンテンツ
  2. 最初の二つli 3へのコンテンツ
  3. 最初の3つのli 4を読んでいました
  4. 最初の4個のli削除しました

私たちは、参照全体に導いた要素を削除することができulリフレッシュシーケンスを。 DOMの操作はJSの変数よりもはるかに遅く、このような無駄な操作は避けてください。

だから問題はどこにあるの? ユーザーは配列の最初の要素を削除し、配列要素全体を前方に移動させますが、角度はユーザーがこのような削除操作を行ったことを知ることができず、目的の要素を押すのは愚かなだけです。

そのため、配列の各項目をマークするメカニズムを導入するだけです。 だから、角度が導入されたtrack by

詳細によるトラック

配列要素をマーキングするために使用されるものは、配列内のIDに似た値でなければなりません。 この値は、次の2つの特性を満たす必要があります。

  1. 繰り返すことはできません。 IDは何のゴーストを繰り返す
  2. 値は単純でなければなりません。 IDは等価性を比較するために使用されますが、異なるアルゴリズムのためにサイズを比較することが可能な場合もあります。

これらの2つの特性に基づいています。 ユーザーはしていない場合はng-repeat指定track by組み込み関数がデフォルト、表現上記のIDを$$idチェックするitem命名されていない$$hashKey` 的成员。如有,返回其值;如没有,则生成一个新的唯一值写入。这就是数组中那个奇怪的`$$hashKey $$hashKey` 的成员。如有,返回其值;如没有,则生成一个新的唯一值写入。这就是数组中那个奇怪的`$$hashKey $$hashKey` 的成员。如有,返回其值;如没有,则生成一个新的唯一值写入。这就是数组中那个奇怪的`$$hashKey 、デフォルト値が起源のメンバーである"object:X" (あなたはなぜ代わりに私が知っていますか数の文字列…私に尋ねます?)

それとも前の質問、の導入track byその後、実行します。 何も指定がないためtrack by 、がデフォルト$id(item) 、実際には$$hashKey

<ul ng-init="array = [
  { value: 1 },
  { value: 2 },
  { value: 3 },
  { value: 4 },
]">
  <li ng-repeat="item in array track by $id(item)" ng-bind="item.value"></li>
</ul>

最初に、 $id(item) 、すべてのアレイを作成する$$hashKey

この時点で、角度は配列の初期状態を次のように記録します。

[
  { "value": 1, "$$hashKey": "object:1" },
  { "value": 2, "$$hashKey": "object:2" },
  { "value": 3, "$$hashKey": "object:3" },
  { "value": 4, "$$hashKey": "object:4" }
]

ユーザーがボタンをクリックすると、配列の最初の要素が削除され、配列は次のようになります。

[
  { "value": 2, "$$hashKey": "object:2" },
  { "value": 3, "$$hashKey": "object:3" },
  { "value": 4, "$$hashKey": "object:4" }
]

最初の比較track byエレメント、ここで$id(item) 、つまり$$hashKey

  1. "object:1" => "object:2"
  2. "object:2" => "object:3"
  3. "object:3" => "object:4"
  4. "object:4" =>未定義

2つは正しくない。つまり、配列に追加された要素または削除された要素または要素が移動されたことを示す。 それを正式化する

  1. "object:1" =>未定義
  2. "object:2" => "object:2"
  3. "object:3" => "object:3"
  4. "object:4" => "object:4"

明らかに、最初の要素は削除されます。 残りの要素を比較する

  1. array[0].value = 2 => array[0].value = 2
  2. array[1].value = 3 => array[1].value = 3
  3. array[2].value = 4 => array[2].value = 4

結論は次のとおりです。

  1. 元の配列の最初の要素が削除されます。
  2. その他は変更されていない

新旧の角度配列によってtrack by要素diffは、ユーザーの行動、動作のDOMツリーの最大可能低下が推測行い、これがあるtrack byの有用性。

ピットによるデフォルトトラック

しかし、需要はいつか変わりました。プログラマXiao Geは、配列をマップしてレンダリングするためにフィルタを使用することに決めました。

<ul ng-init="array = [
  { value: 1 },
  { value: 2 },
  { value: 3 },
  { value: 4 },
]">
  <li ng-repeat="item in array | myMap" ng-bind="item.value"></li>
</ul>

マップは次のように定義されます。

xxModule.filter('map', function () {
  return arr => arr.map(item => ({ value: item.value + 1 }));
});

ng-repeat実行するときに式を評価するためにarray | myMap値を:

arrayForNgRepeat = [
  { value: 2 },
  { value: 3 },
  { value: 4 },
  { value: 5 },
]

アレイことに注意arrayForNgRepeatと元の配列のarray同じ参照ないfilter地図操作に新しいオブジェクト、元の配列とは無関係である各々が新しい配列を生成します。

ng-repeat時間は、ユーザーが角度を指定していないことを発見track byデフォルトロジック、使用に応じて、 $id(item)などtrack byの追加、 $$hashKey

arrayForNgRepeat = [
  { value: 2, "$$hashKey": "object:1" },
  { value: 3, "$$hashKey": "object:2" },
  { value: 4, "$$hashKey": "object:3" },
  { value: 5, "$$hashKey": "object:4" },
]

DOMを生成する:

  • 2
  • 3
  • 4
  • 5

ここでもまた注意してください:配列arrayForNgRepeatは、元の配列配列とは何の関係もありません。配列自体は異なる参照であり、配列内の各項目も異なる参照です。 新しい配列のメンバーを変更しても元の配列には影響しません。

この時点で、array:

array = [
  { value: 1 },
  { value: 2 },
  { value: 3 },
  { value: 4 },
]

この時点で、ユーザの無関係の操作は、ダーティチェックをトリガする。 以下のためにng-repeat表現、最初の計算のarray | myMap値:

newArrayForNgRepeat = [
  { value: 2 },
  { value: 3 },
  { value: 4 },
  { value: 5 },
]

最初の比較track by要素。 ユーザー指定されていないが、デフォルトは$id(item)

$idいくつかの要素が配列であるが、何もありません見つけ$$hashKey`,则给它们填充新`$$hashKey 、結果は

newArrayForNgRepeat = [
  { value: 2, "$$hashKey": "object:5" },
  { value: 3, "$$hashKey": "object:6" },
  { value: 4, "$$hashKey": "object:7" },
  { value: 5, "$$hashKey": "object:8" },
]

両方の側面track byの実績

  1. "object:1" => "object:5"
  2. "object:2" => "object:6"
  3. "object:3" => "object:7"
  4. "object:4" => "object:8"

2つは正しくない。つまり、配列に追加された要素または削除された要素または要素が移動されたことを示す。 それを正式化する

  1. "object:1" =>未定義
  2. "object:2" =>未定義
  3. "object:3" =>未定義
  4. "object:4" =>未定義
  5. =>未定義"object:5"
  6. =>未定義"object:6"
  7. =未定義> "object:7"
  8. =>未定義"object:8"

結論は次のとおりです。

  1. 元の配列の4つの要素がすべて削除されます
  2. 4つの新しい要素を追加

だから、すべての元の角度li削除してから、4つの新しい作成li要素を、その充填textContentに、 ul

私が話していると思われる場合は、ブラウザで自分でテストしてください。 デバッグツールでDOMツリーの点滅をはっきりと確認できます。

track byとパフォーマンス

不適切なng-repeatフリッカーのページで、その結果、ブラウザの応答時間が遅く、DOMツリーを繰り返し再構築なります。 上記のこの極端な場合に加えて、その後も手動で追加する必要があるため、頻繁にサーバー側のデータを引っ張るのリストが更新している場合track byたデータのフロントエンドへのインタフェースが含まれていることは不可能であるため、 $$hashKeyこの種のものを、その結果はリストが頻繁に再構築されます。

実際には、少なくとも生成角度を避けるために、短期では、それほど考慮する必要がない、プラス害はありません$$hashKeyこの奇妙なものを。 だから

教えてくださいng-repeat手動で追加track by

もう一度言いたいこと

教えてくださいng-repeat手動で追加track by

通常、リストはデータベースから読み取られた要求インターフェースによって戻されます。 通常、データベースのレコード持ちid主キーとしてフィールドを、その後、使用する際にidようtrack byフィールドが最良の選択です。 そうでない場合は、いくつかのビジネス分野を選択できますが、重複していないことを確認してください。 例えば、ヘッダも、動的に生成されたテーブルである、として用いることができるヘッダフィールド名track byフィールドは、(キーオブジェクトが繰り返されません)。

あなたは本当に見つけることができない場合track by 、角度が自動的に生成されるように、フィールド$$hashKeyもできませんが、外部の点滅デバッグツールのDOMツリーに近い外観に加えて、現象は表示されません再磨く常にDOM要素をチェックすることを忘れないでください、要素のリストに特別なタグを追加する(例えば、 style="background: red" )(このフラグが誤ってクリアされている場合、元のDOM要素が除去されたことを示す)、それはまた、有効な方法です。

本当にないない限り、我々は使用することはお勧めしません$indextrack byフィールド。

track byして、単一の結合

track byだけ角度再利用し、既存のDOM要素を聞かせて。 配列の各子要素の内側のバインディング式の汚れたチェックはやはり避けられません。 ただし、実際のアプリケーションのシナリオでは、アレイの全体的な変更(ページングなど)が頻繁に行われ、アレイの各項目は通常は個別に変更されません。 そして、あなたは、単一の大幅な削減を使用してバインドすることができます$watch式の数を。 例えば

<li ng-repeat="item in array track by item.id">
  <div>a: <span ng-bind="::item.a"></span></div>
  <div>b: <span ng-bind="::item.b"></span></div>
  <div>c: <span ng-bind="::item.c"></span></div>
  <div>d: <span ng-bind="::item.d"></span></div>
  <div>e: <span ng-bind="::item.e"></span></div>
</li>

場合を除きtrack by DOMツリーの再構築に起因するフィールドの変更、 item.aなどは一度、もはや監視された後のページに表示されます。

各行は、5つのバインディング式、レコードにつき20を有している場合、この方法は、ページ削減することができる5 * 20 = 100結合発現をモニター番目。

注意:場合はng-repeat単一の内部結合の使用は、それは使用してはならないtrack by $index 。 そうしないと、ユーザーは次のページを更新しません。

バインディングの数を減らすためにページングを使用する

これはあまり言わない。 バックエンドでは、バックエンドのページネーションをページングすることができ、フロントページのインターフェイスは、ページネーションをサポートする必要はありませんが、ときあなたは単にフロントページを記述することができfilterArray.prototype.slice達成。

あなたは、直接ではない配列内のアイテムの数を減らすことができng-repeat 、各項目に書かれたng-showng-if

最後の言葉で書かれている

実際、この問題の徹底的な検査は、3つの主要なフロントエンドフレームワークに多かれ少なかれ関与しています。 反応するたびに新しく生成されたVirtual DOM 、古いVirtual DOMのdiffの動作は、既に汚いチェックとして見ることができます。 比較的汚れからVueが完全に使い、チェック機構を放棄したPropertyアクティブトリガUIの更新を、まだのVueの放棄することはできませんtrack byこの事。

3つの主流のフレームワークで汚れたチェックが多かれ少なかれ保存されるため、Angularのパフォーマンスは広く批判されていますか? 実際、角度1のメカニズムの下では、ダーティチェックの実行範囲が大きすぎ、頻度が高すぎると言われています。 角度からの角度1.5 2+のコンポーネントを導入しました( Component )コンセプトは、しかし、神の非形状が、それは本当にただの特別なDirectiveのさまざまなコンポーネントに限定され、それが汚れて行う範囲チェックを持っていない権利が確定し、したがって、それは本質的にすることはできませんAngular 1 Dirty Checkメカニズムの非効率性を変更してください。

たぶん角度1は最終的に排除されるでしょう。 しかし、Angularはフロントエンドの最初のMVVMフレームワークであり、実際にフロントエンドフレームワークの更新が急増しました。 ワームは堅くならずに死んでしまい、とにかくコンピュータにとどまるAngular 1プロジェクトを引き続き維持しなければなりません。 しかし、ボスは非常に思いやりのあるもので、プロジェクト全体をVueでリファクタリングする時間を提供します。将来を知っているのは誰ですか?

元のリンク