javascript - 違い - AngularJSのデータバインディングはどのように機能しますか?
angularjs 読み方 (10)
$scope
オブジェクトをダーティーチェックすることによって
Angularは、 $scope
オブジェクト$scope
ウォッチャーの単純なarray
を維持します。 $scope
を調べると、 $$watchers
というarray
が含まれていることがわかります。
各ウォッチャは、他のものの中にも含まれるobject
です
- ウォッチャーが監視している表現。 これは単に
attribute
名でもよいし、もっと複雑なものでもよい。 - 式の最後の既知の値。 これは、式の現在の計算値と照合することができます。 値が異なる場合、ウォッチャーは関数をトリガし、
$scope
をdirtyとしてマークします。 - ウォッチャーが汚れている場合に実行される関数。
ウォッチャーの定義方法
AngularJSにウォッチャーを定義するには、さまざまな方法があります。
$scope
上で明示的に$watch
attribute
を$watch
ことができ$scope
。$scope.$watch('person.username', validateUnique);
テンプレートに
{{}}
補間を置くことができます(ウォッチャーは現在の$scope
で作成されます)。<p>username: {{person.username}}</p>
ウォッチャーを定義するために
ng-model
などのディレクティブを要求することができます。<input ng-model="person.username" />
$digest
サイクルは、すべてのウォッチャーを最後の値と比較してチェックします
通常のチャンネル(ng-model、ng-repeatなど)でAngularJSとやりとりするとき、ダイジェストサイクルが指令によって引き起こされます。
ダイジェストサイクルは、 $scope
とそのすべての子の深さ優先トラバースです 。 $scope
object
ごとに$$watchers
array
を反復処理し、すべての式を評価します。 新しい式の値が最後の既知の値と異なる場合、ウォッチャーの関数が呼び出されます。 この関数は、DOMの一部を再コンパイルしたり、 $scope
値を再計算したり、 AJAX
request
トリガーしたり、必要な処理を行うことがあります。
すべてのスコープがトラバースされ、すべてのウォッチ式が評価され、最後の値と照合されます。
ウォッチャーが起動された場合、 $scope
は汚れています
ウォッチャーがトリガーされた場合、アプリは何かが変更されたことを知り、 $scope
はダーティーとしてマークされます。
Watcher関数は、 $scope
または親$scope
他の属性を変更することができ$scope
。 1つの$watcher
関数がトリガされた場合、他の$scope
がまだきれいであるとは保証できませんので、ダイジェストサイクル全体を再度実行します。
これは、AngularJSには双方向バインディングがあるため、 $scope
ツリーにデータを戻すことができるためです。 既に消化された$scope
値を変更することがあります。 おそらく$rootScope
値を変更するでしょう。
$digest
がダーティである場合、 $digest
$digest
サイクル全体を再度実行します
ダイジェストサイクルがきれいになる( $watch
式はすべて前のサイクルと同じ値になる)か、ダイジェスト制限に達するまで$digest
サイクルを繰り返しループします。 デフォルトでは、この制限は10に設定されています。
ダイジェスト制限に達すると、AngularJSはコンソールにエラーを発生させます:
10 $digest() iterations reached. Aborting!
ダイジェストはマシン上では難しいが、開発者にとっては簡単だ
ご覧のとおり、AngularJSアプリで何か変更があるたびに、AngularJSは$scope
階層内のすべてのウォッチャーをチェックして対応する方法を確認します。 開発者にとって、これは膨大な生産性をもたらします。配線コードをほとんど書き込まなくても、AngularJSは値が変更された場合に気付き、残りの部分を変更と整合させます。
マシンの視点から見ると、これは非常に効率が悪く、あまりにも多くのウォッチャーを作成するとアプリケーションが遅くなります。 Miskoは、あなたのアプリが古いブラウザでは遅く感じる前に、約4000人のウォッチャーの人物を引用しています。
たとえば、大規模なJSON
array
に対してng-repeat
を実行すると、この制限に達するのは簡単です。 ウォッチャーを作成せずにテンプレートをコンパイルするためのワンタイムバインドなどの機能を使用すると、これを軽減できます。
あまりにも多くのウォッチャーを作成しないようにする方法
ユーザーがあなたのアプリとやりとりするたびに、アプリ内のすべてのウォッチャーが少なくとも1回評価されます。 AngularJSアプリケーションを最適化する大きな部分は、 $scope
ツリー$scope
ウォッチャー数を減らすことです。 これを行う簡単な方法の1つは、 時間を拘束することです。
まれにしか変更されないデータがある場合、次のように::構文を使用して一度だけバインドできます。
<p>{{::person.username}}</p>
または
<p ng-bind="::person.username"></p>
バインディングは、インクルードテンプレートがレンダリングされ、データが$scope
ロードされたときにのみトリガされ$scope
。
これは、多くのアイテムでng-repeat
がある場合に特に重要です。
<div ng-repeat="person in people track by username">
{{::person.username}}
</div>
AngularJS
フレームワークのデータバインディングはどのように機能しますか?
自分のサイトに技術的な詳細が見つかりませんでした。 データがビューからモデルに伝播するときの動作は、多かれ少なかれ明確です。 しかし、AngularJSは、セッターやゲッタなしでモデルプロパティの変更をどのように追跡しますか?
私は、この作業を行う可能性があるJavaScriptウォッチャーがあることを発見しました。 ただし、 Internet Explorer 6およびInternet Explorer 7 Internet Explorer 6サポートされていません。 AngularJSは、私が次の例を変更し、この変更をビューに反映したことをどうやって知っていますか?
myobject.myproperty="new value";
一方向データバインディングは、値がデータモデルから取得され、HTML要素に挿入されるアプローチです。 モデルをビューから更新する方法はありません。 これは古典的なテンプレートシステムで使用されます。 これらのシステムは、一方向にのみデータをバインドします。
角度アプリケーションのデータバインディングは、モデルとビューコンポーネント間のデータの自動同期です。
データバインディングを使用すると、アプリケーションでモデルを真の単一のソースとして扱うことができます。 このビューは、常にモデルの投影です。 モデルが変更された場合、ビューは変更を反映し、その逆も同様です。
AngularJSは、 $watch() 、 $digest() 、 $apply() 3つの強力な関数の助けを借りて、データバインディングのメカニズムを扱います。 ほとんどの場合、AngularJSは$ scope。$ watch()と$ scope。$ digest()を呼び出しますが、場合によってはこれらの関数を手動で呼び出して新しい値で更新する必要があります。
$ watch() : -
この関数は、$ scope上の変数の変更を観察するために使用されます。 これは、式、リスナ、および等価オブジェクトの3つのパラメータを受け取ります。リスナと等価オブジェクトはオプションのパラメータです。
$ digest() -
この関数は、$ scopeオブジェクト内のすべてのウォッチとその子$ scopeオブジェクトを反復処理します
(もしあれば)。 $ digest()が時計を反復すると、式の値が変更されたかどうかをチェックします。 値が変更された場合、AngularJSは新しい値と古い値でリスナーを呼び出します。 $ digest()関数は、AngularJSが必要と考えるときはいつでも呼び出されます。 たとえば、ボタンをクリックした後、またはAJAX呼び出しの後などです。 AngularJSがあなたのために$ digest()関数を呼び出さない場合があります。 その場合は、自分で呼び出す必要があります。
$ apply() -
Angular doは、AngularJSコンテキスト内にあるモデルの変更のみを自動的に更新します。 Angularコンテキスト(ブラウザのDOMイベント、setTimeout、XHR、サードパーティのライブラリなど)の外でモデルを変更する場合は、$ apply()を手動で呼び出してAngularに変更を通知する必要があります。 $ apply()関数呼び出しが終了すると、AngularJSは$ digest()を内部的に呼び出し、すべてのデータバインディングが更新されます。
AngularJSはその値を記憶し、それを以前の値と比較します。 これは基本的な汚れチェックです。 値に変更がある場合、変更イベントが発生します。
$apply()
メソッドは、AngularJS以外の世界からAngularJS世界に移行するときに呼び出すもので、 $digest()
呼び出します。 ダイジェストは単なる普通の汚れチェックです。 それはすべてのブラウザで動作し、完全に予測可能です。
汚れチェック(AngularJS)対変更リスナー( KnockoutJSとBackbone.js ):汚れチェックはシンプルに見えるかもしれませんが、非効率でも(後で取り上げます)、常に意味的に正しいと判明します。変更リスナーには奇妙なコーナーケースがたくさんあり、意味的に正確にするために依存性トラッキングなどが必要です。 KnockoutJSの依存関係追跡は、AngularJSにはない巧妙な機能です。
変更リスナーに関する問題:
- ブラウザはネイティブにそれをサポートしていないので、構文は凶悪です。 はい、プロキシがありますが、すべての場合において意味的に正しいわけではありません。もちろん、古いブラウザにはプロキシはありません。 要点は、ダーティチェックでPOJOを行うことができますが、KnockoutJSとBackbone.jsではクラスから継承し、アクセサーを介してデータにアクセスすることが強制されます。
- 合体を変更する。 アイテムの配列があるとします。 変更するたびにイベントを発生させてUIをレンダリングするたびに、追加するループとして、配列に項目を追加したいとします。 これはパフォーマンスにとって非常に悪いことです。 あなたが望むのは、最後に一度だけUIを更新することです。 変更イベントが細かすぎます。
- チェンジリスナーはさらに多くの変更イベントを発生させるデータをさらに変更できるため、リスナーはセッターですぐに変更されます。これは問題です。 あなたのスタックでは、一度にいくつかの変更イベントが発生する可能性があるので、これは悪いことです。 何らかの理由で同期しておく必要がある2つの配列があるとします。 あなたはどちらか一方にしか追加することができませんが、追加するたびに変更イベントが発生し、変更イベントが発生し、世界が一貫して表示されなくなります。 これはスレッドロックに非常によく似た問題です.JavaScriptは、各コールバックが排他的に完了して実行されるので回避します。 変更イベントは、セッターが意図していない、明らかではない遠大な結果をもたらす可能性があるため、スレッドの問題を全面的に引き起こします。 リスナーの実行を遅延させ、一度に1つのリスナーしか実行しないようにすることで、任意のコードが自由にデータを変更でき、実行中に他のコードが実行されないことがわかります。
パフォーマンスはどうですか?
汚れチェックは非効率的なので、遅いと思われるかもしれません。 これは、単に理論的な議論ではなく、実数を見る必要があるところですが、まずいくつかの制約を定義しましょう。
人間は:
遅い - 50ミリ秒より早いものは人間には知覚できず、したがって「即時」とみなすことができます。
限定 - 実際には、1ページに人間に約2000以上の情報を表示することはできません。 それ以上のものは本当に悪いUIであり、人間はこれをとにかく処理できません。
したがって、本当の問題は次のとおりです。ブラウザで50 msで何回比較できますか? これは、多くの要素が出てくるので答えが難しい質問ですが、テストケースはhttp://jsperf.com/angularjs-digest/6で、10,000人のウォッチャーが作成されています。 現代的なブラウザでは、これはわずか6ミリ秒です。 Internet Explorer 8では約40ミリ秒かかります。 ご覧のとおり、これは遅いブラウザーでも問題ではありません。 注意が必要です:比較は時間制限に合わせるのが簡単である必要があります...残念ながらAngularJSに遅い比較を追加するのは簡単すぎるので、遅いアプリケーションを構築するのは簡単です。やっている。 しかし、私たちは、あなたが遅い比較であることを示す計装モジュールを提供することによって答えを得ることを望みます。
ビデオゲームやGPUは、一貫性があるため、ダーティチェックの手法を採用していることが判明しました。 モニターのリフレッシュレート(通常50〜60Hz、または16.6〜20ms)を超える限り、それ以上のパフォーマンスは無駄です.FPSを高くするよりも、より多くのものを描画する方がよいでしょう。
Miskoは既にデータバインディングの仕組みについて優れた説明をしていますが、データバインディングのパフォーマンス上の問題について私の見解を加えたいと思います。
Misko氏の述べたように、2000年前後のバインディングは問題を見始める場所ですが、とにかくページには2000以上の情報があるべきではありません。 これは当てはまりますが、すべてのデータバインディングがユーザーに見えるわけではありません。 双方向バインディングを使用して任意の種類のウィジェットまたはデータグリッドを構築すると、悪いUXを持たずに簡単に 2000バインディングをヒットできます 。
たとえば、テキストを入力して使用可能なオプションをフィルタできるコンボボックスを考えてみましょう。 この種の制御は〜150個のアイテムを持つことができ、依然として高度に使用可能です。 いくつかの特別な機能(例えば、現在選択されているオプションの特定のクラスなど)がある場合、オプションごとに3〜5のバインディングを取得し始めます。 これらのウィジェットのうちの3つをページに入れます(たとえば、国を選択する国、その国の都市を選択する国、およびホテルを選択する第3の国)。あなたはすでに1000から2000までのバインディングを使用しています。
また、企業のWebアプリケーションでデータグリッドを検討することもできます。 ページあたり50行が不合理ではなく、それぞれ10〜20列を持つことができます。 これをng-repeatsで構築したり、いくつかのバインディングを使用するいくつかのセルに情報がある場合、このグリッドだけで2000個のバインディングに近づいている可能性があります。
AngularJSで作業するときにこれが大きな問題になることがわかりました。これまでに見つけられた唯一の解決策は、ngOnceを使用する代わりに、双方向バインディングを使用せずにウィジェットを構築すること、ウォッチャーや類似のトリックを登録解除すること、 jQueryとDOM操作でDOMを構築するディレクティブ。 私はこれが最初にAngularを使う目的を打ち負かしたと感じています。
私はこれに対処するための他の方法の提案を聞いてほしいですが、おそらく私自身の質問を書くべきでしょう。 私はこれをコメントに入れたいと思っていましたが、それはあまりにも長くなることが判明しました...
TL; DR
データバインディングは、複雑なページでパフォーマンスの問題を引き起こす可能性があります。
データバインディング:
データバインディングとは何ですか?
ユーザーがビュー内のデータを変更すると、その変更がスコープ・モデルで更新され、その変更が行われます。
どのように可能ですか?
短い答え:ダイジェストサイクルの助けを借りて。
説明: Angular jsはスコープモデル上のウォッチャーを設定します。ウォッチャーは、モデルに変更があった場合にリスナー関数を起動します。
$scope.$watch('modelVar' , function(newValue,oldValue){
//新しい値でコードを更新する
});
では、いつ、どのようにウォッチャー関数が呼び出されますか?
ウォッチャー関数は、ダイジェストサイクルの一部として呼び出されます。
ダイジェストサイクルは、ng-model、ng-bind、$ timeout、ng-clickなどのディレクティブ/サービスに組み込まれた角度jの一部として自動的にトリガーされ、ダイジェストサイクルをトリガーします。
ダイジェストサイクル機能:
$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope
すなわち$rootScope.$apply()
注意:$ apply()は$ rootScopeと同じです。$ digest()これは、ダーティーチェックが、ルートjまたは親スコープから角度jsアプリケーション内のすべての子$スコープまで、右から始まることを意味します。
上記の機能は、あなたのアプリケーションがスクリプトタグで参照されているangularjsフレームワークスクリプトファイルを使用していることを意味する角度jsアプリケーションであることを確認するだけで、上記のバージョンのブラウザIEでも動作します。
ありがとうございました。
写真で説明する:
データバインディングにはマッピングが必要です
スコープ内の参照は、テンプレート内の参照とまったく同じではありません。 2つのオブジェクトをデータバインドする場合は、最初のオブジェクトをリスンしてもう一方のオブジェクトを変更する3つ目のオブジェクトが必要です。
ここで、 <input>
を変更すると、 data-ref3をタッチします 。 そして、古典的なデータ結合主義はdata-ref4を変更します 。 では、他の{{data}}
式はどのように動くのでしょうか?
イベントは$ digest()につながります
AngularはすべてのバインディングのoldValue
とnewValue
を保持します。 Angularイベントが終了するたびに、有名な$digest()
ループがWatchListをチェックして何か変わったかどうかを調べます。 これらのAngularイベントは、 ng-click
、 ng-change
、 $http
completed ... $digest()
は、 oldValue
がnewValue
と異なる限りループします。
前の画像では、data-ref1とdata-ref2が変更されたことがわかります。
結論
それは、卵と鶏のようなものです。 誰が始めているのかは決して分かりませんが、予想どおりほとんどの時間はうまくいきます。
もう1つのポイントは、メモリとCPU上の単純なバインドの深いインパクトを簡単に理解できることです。 うまくいけば、デスクトップはこれを処理するのに十分な太さです。 携帯電話はそれほど強くはありません。
明らかに、それに接続されたオブジェクトに変更があるかどうかの定期的なScope
チェックはありません。 スコープに添付されたオブジェクトのすべてが監視されるわけではありません。 スコープはプロトタイプ的に$$ウォッチャーを維持します。 Scope
は、 $digest
が呼び出されたときにのみ、この$$watchers
反復します。
Angularは、これらのそれぞれの$$ウォッチャーにウォッチャーを追加します
- {{式}} - テンプレート内(および式がある場所)またはng-modelを定義するとき。
- $ scope。$ watch( 'expression / function') - あなたのJavaScriptでは、ウォーニングのためのスコープオブジェクトを添付することができます。
$ watch関数は3つのパラメータを取ります:
最初はオブジェクトを返すウォッチャー関数か、式を追加するだけです。
2つ目は、オブジェクトに変更があったときに呼び出されるリスナー関数です。 DOMの変更のようなものはすべてこの関数で実装されます。
3番目の引数はブール値をとるオプションのパラメータです。 真の角度の深いディープがオブジェクトを監視し、その偽の角度がオブジェクトを参照するだけの場合。 $ watchの粗い実装は次のようになります
Scope.prototype.$watch = function(watchFn, listenerFn) {
var watcher = {
watchFn: watchFn,
listenerFn: listenerFn || function() { },
last: initWatchVal // initWatchVal is typically undefined
};
this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers
};
AngularにはDigest Cycleという面白いことがあります。 $ digest()は$ scope。$ digest()の呼び出しの結果として開始されます。 ng-clickディレクティブを使用してハンドラ関数の$ scopeモデルを変更するとします。 この場合、AngularJSは自動的に$ digest()を呼び出すことで$ digestサイクルを起動します.ng-clickの他に、モデルを変更するためのいくつかの組み込みディレクティブ/サービスがあります(ng-model、$ timeoutなど) $ダイジェストサイクルを自動的に起動します。 $ digestの大まかな実装は次のようになります。
Scope.prototype.$digest = function() {
var dirty;
do {
dirty = this.$$digestOnce();
} while (dirty);
}
Scope.prototype.$$digestOnce = function() {
var self = this;
var newValue, oldValue, dirty;
_.forEach(this.$$watchers, function(watcher) {
newValue = watcher.watchFn(self);
oldValue = watcher.last; // It just remembers the last value for dirty checking
if (newValue !== oldValue) { //Dirty checking of References
// For Deep checking the object , code of Value
// based checking of Object should be implemented here
watcher.last = newValue;
watcher.listenerFn(newValue,
(oldValue === initWatchVal ? newValue : oldValue),
self);
dirty = true;
}
});
return dirty;
};
JavaScriptのsetTimeout()関数を使用してスコープモデルを更新すると、Angularは変更する可能性のあることを知ることができません。 この場合、$ apply()を手動で呼び出すのは私たちの責任です。これは$ダイジェストサイクルをトリガーします。 同様に、DOMイベントリスナーを設定し、ハンドラ関数内のいくつかのモデルを変更する指示がある場合は、$ apply()を呼び出して変更が有効になるようにする必要があります。 $ applyの大きなアイデアは、Angularを認識しないコードを実行できることです。そのコードはスコープ上のものを変更する可能性があります。 そのコードを$ applyにラップすると、$ digest()の呼び出しが処理されます。 $ apply()の大まかな実装。
Scope.prototype.$apply = function(expr) {
try {
return this.$eval(expr); //Evaluating code in the context of Scope
} finally {
this.$digest();
}
};
私はしばらくこのことを疑問に思いました。 設定者がいなければ、 AngularJS
は$scope
オブジェクトの変更をどのように通知しますか? それを投票するのですか?
実際にはこれがあります:あなたがモデルを修正する "正常な"場所は既にAngularJS
から呼び出されていたので、コード実行後に$apply
自動的に呼び出されます。 あなたのコントローラにいくつかの要素をng-click
する方法があるng-click
。 AngularJS
はあなたのためにそのメソッドの呼び出しをAngularJS
ので、適切な場所に$apply
を行う機会があり$apply
。 同様に、ビューの中に現れる式の場合、それらはAngularJS
によって実行されるので、 $apply
実行し$apply
。
マニュアルがAngularJS
以外のコードに対して$apply
手動で呼び出す必要があることについて話しているとき、実行時に呼び出しスタック内のAngularJS
自体に由来しないコードについて説明しています。
私は人のデータモデルをフォームとリンクさせる必要がありました。私がしたことは、フォームとデータの直接マッピングでした。
たとえば、モデルに次のようなものがあるとします。
$scope.model.people.name
フォームの制御入力:
<input type="text" name="namePeople" model="model.people.name">
こうすることで、オブジェクトコントローラの値を変更すると、ビューに自動的に反映されます。
サーバーデータからモデルを渡した例は、郵便番号と郵便番号に書かれた書式に基づいて、そのビューに関連付けられたコロニーと都市のリストを要求し、デフォルトでユーザーに最初の値を設定します。 そして、私は非常にうまくいった、何が起こるかは、 angularJS
がモデルをリフレッシュするのに数秒かかることがあります。これを行うには、データを表示しながらスピナーを置くことができます。