AngularJS
フレームワークでデータバインディングはどのように機能しますか?
彼らのサイト で技術的な詳細を見つけることができませんでした。データがビューからモデルに伝播されたときにどのように機能するかは、ほぼ明らかです。しかし、AngularJSは、セッターやゲッターなしでモデルプロパティの変更をどのように追跡するのでしょうか。
私は JavaScriptウォッチャーがいることを発見しました これはこの仕事をするかもしれません。しかし、 Internet Explorer 6 および Internet Explorer 7 ではサポートされていません。では、AngularJSは、たとえば次のように変更したことをどのようにしてビューに反映したのでしょうか。
myobject.myproperty="new value";
AngularJSは値を記憶し、それを以前の値と比較します。これは基本的なダーティーチェックです。値が変化した場合は、changeイベントが発生します。
$apply()
メソッドは、非AngularJSの世界からAngularJSの世界に移行するときに呼び出すもので、$digest()
を呼び出します。ダイジェストは単なる古いダーティチェックです。すべてのブラウザで動作し、完全に予測可能です。
ダーティーチェック(AngularJS)とチェンジリスナー( KnockoutJS および Backbone.js )を対比すると、ダーティーチェックは単純に見え、非効率的でさえあるかもしれませんが(後で説明します)。それは常に意味論的に正しいですが、変更リスナーは奇妙なコーナーケースをたくさん持っていて、それをより意味論的に正しいものにするために依存関係追跡のようなものが必要です。 KnockoutJS依存関係追跡は、AngularJSにはない問題に対する賢い機能です。
したがって、ダーティーチェックは非効率的であるため、遅くなっているように見えます。これが、理論的な議論ではなく実数を調べる必要があるところですが、最初にいくつかの制約を定義しましょう。
人間は:
遅い - 50ミリ秒より速いものは人間には知覚できないため、「インスタント」と見なすことができます。
限られた - あなたは本当に1ページで人間に約2000個以上の情報を見せることはできません。それ以上のものは本当に悪いUIであり、人間はとにかくこれを処理することができません。
それで本当の質問はこれです:あなたは50ミリ秒の間にどれぐらいの比較をあなたがブラウザですることができますか?これは多くの要因が関係してくるので答えるのは難しい質問ですが、これはテストケースです: http://jsperf.com/angularjs-digest/6 これは10,000人のウォッチャーを作成します。最近のブラウザでは、これは6ミリ秒弱かかります。 On Internet Explorer 8 約40 msかかります。ご覧のとおり、最近の遅いブラウザでもこれは問題になりません。警告があります:比較は制限時間に収まるように簡単である必要があります...残念ながらAngularJSに遅い比較を追加するのは非常に簡単なので、あなたが何を知らないか遅いアプリケーションを構築するのは簡単です。やっている。しかし、私たちはインストルメンテーションモジュールを提供することによって答えを得たいと思っています。
ビデオゲームとGPUがダーティチェックアプローチを使用していることがわかります。これは特に一貫性があるためです。モニタのリフレッシュレート(通常は50〜60 Hz、または16.6〜20 msごと)を超える限り、それ以上のパフォーマンスは無駄になります。そのため、FPSを高くするよりも、より多くのものを描画することをお勧めします。
Miskoは既にデータバインディングの仕組みについて優れた説明を行っていますが、データバインディングのパフォーマンスの問題に関する見解を追加したいと思います。
Miskoが述べたように、2000前後のバインディングで問題が発生し始めますが、とにかく1ページに2000以上の情報を含めるべきではありません。これは本当かもしれませんが、すべてのデータバインディングがユーザーに表示されるわけではありません。双方向バインディングを使用して何らかのウィジェットまたはデータグリッドの構築を開始すると、悪いUXがなくても、2000バインディングを簡単にヒットできます。
たとえば、使用可能なオプションをフィルタリングするためにテキストを入力できるコンボボックスを検討してください。この種のコントロールには、〜150個のアイテムを含めることができ、それでも非常に使いやすくなります。追加の機能(現在選択されているオプションの特定のクラスなど)がある場合は、オプションごとに3〜5個のバインディングを取得し始めます。これらのウィジェットのうち3つをページに配置し(たとえば、1つは国を選択し、もう1つはその国の都市を選択し、3つ目はホテルを選択します)、あなたは既に1000から2000のバインディングのどこかにいます。
または、企業Webアプリケーションのデータグリッドを検討してください。 1ページあたり50行は無理ではありません。各行には10〜20列を含めることができます。 ng-repeatsでこれを構築する場合、および/またはいくつかのバインディングを使用するいくつかのセルに情報がある場合、このグリッドだけで2000個のバインディングに近づくことができます。
これはAngularJSで作業するときにhugeの問題であり、これまで見つけた唯一の解決策は、ngOnceを使用する代わりに、双方向バインディングを使用せずにウィジェットを構築することですウォッチャーと同様のトリック、またはjQueryとDOM操作でDOMを構築するディレクティブを作成します。そもそもAngularを使用する目的に反すると感じます。
これを処理する他の方法についての提案を聞きたいと思いますが、それから私は自分の質問を書く必要があります。これをコメントに入れたかったのですが、そのためには長すぎます...
TL; DR
データバインディングは、複雑なページでパフォーマンスの問題を引き起こす可能性があります。
$scope
オブジェクトをダーティーチェックすることによってAngularは$scope
オブジェクト内にウォッチャーの単純なarray
を維持します。 $scope
を調べると、$$watchers
というarray
が含まれていることがわかります。
各ウォッチャーは、とりわけobject
であり、
attribute
の名前、あるいはもっと複雑な名前かもしれません。$scope
をダーティとしてマークします。AngularJSでウォッチャーを定義するには、さまざまな方法があります。
$watch
に明示的に$scope
およびattribute
を付けることができます。
$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
はダーティとしてマークされます。
ウォッチャー関数は$scope
上または親$scope
上の他の属性を変更することができます。 1つの$watcher
関数がトリガされた場合、他の$scope
がまだクリーンであることを保証することはできないため、ダイジェストサイクル全体をもう一度実行します。
これは、AngularJSには双方向のバインディングがあるため、データを$scope
ツリーに戻すことができるためです。すでにダイジェストされているより高い$scope
の値を変更することがあります。おそらく$rootScope
の値を変更します。
$digest
が汚れている場合は、$digest
サイクル全体をもう一度実行します。ダイジェストサイクルがクリーンになるまで(すべての$digest
式が前のサイクルと同じ値になるまで)、またはダイジェスト制限に達するまで、$watch
サイクルを繰り返します。デフォルトでは、この制限は10に設定されています。
ダイジェスト制限に達すると、AngularJSはコンソールにエラーを表示します。
10 $digest() iterations reached. Aborting!
ご覧のとおり、AngularJSアプリケーションで何かが変更されるたびに、AngularJSは$scope
階層内のすべてのウォッチャーをチェックして応答方法を確認します。開発者にとってこれは大規模な生産性の恩恵です。配線コードをほとんど記述する必要がなくなるため、AngularJSは値が変更されたかどうかを確認し、それ以外の部分は変更と一致するようにします。
マシンの観点から見ると、これは非常に非効率的であり、作成するウォッチャーが多すぎるとアプリの動作が遅くなります。 Miskoは、古いブラウザではアプリの動作が遅くなる前に、約4000人のウォッチャーの数を引用しました。
たとえば、大きなJSON
array
の上にng-repeat
を付けた場合、この制限に達するのは簡単です。ウォッチャーを作成せずにテンプレートをコンパイルするためのワンタイムバインディングなどの機能を使用して、これを軽減できます。
ユーザーがアプリと対話するたびに、アプリ内のウォッチャーは少なくとも1回は評価されます。 AngularJSアプリを最適化することの大きな部分は、あなたの$scope
ツリー内のウォッチャーの数を減らすことです。これを行う簡単な方法の1つは、 ワンタイムバインディング を使用することです。
めったに変更されないデータがある場合は、::構文を使用して1回しかバインドできません。
<p>{{::person.username}}</p>
または
<p ng-bind="::person.username"></p>
バインディングは、それを含むテンプレートがレンダリングされ、データが$scope
にロードされたときにのみトリガーされます。
あなたがng-repeat
にたくさんのアイテムを持っているとき、これは特に重要です。
<div ng-repeat="person in people track by username">
{{::person.username}}
</div>
これが私の基本的な理解です。それは間違っているかもしれません!
$watch
メソッドに関数を渡すことで監視されます(監視対象のものを返します)。$apply
メソッドでラップされたコードブロック内で行う必要があります。$apply
の最後に$digest
メソッドが呼び出され、各ウォッチを調べて、前回の$digest
の実行以降に変更されたかどうかを確認します。通常の開発では、HTMLのデータバインディング構文はAngularJSコンパイラにウォッチを作成するように指示し、コントローラメソッドはすでに$apply
内で実行されます。そのため、アプリケーション開発者にとっては、それはすべて透明です。
私はしばらくこれを自分で思った。セッターがないと、AngularJS
は$scope
オブジェクトへの変更をどのように認識しますか?それは彼らを世論調査しますか?
それが実際にしていることはこれです:あなたがモデルを修正するどんな "普通の"場所も既にAngularJS
の内部から呼ばれているので、あなたのコードが走った後自動的に$apply
を呼び出します。あなたのコントローラが何らかの要素のng-click
にフックされているメソッドを持っているとしましょう。 AngularJS
はあなたにそのメソッドの呼び出しを結びつけるので、適切な場所で$apply
をする機会があります。同様に、ビューに正しく現れる式の場合、それらはAngularJS
によって実行されるので、$apply
が実行されます。
ドキュメントがコード_ AngularJS
の外側のために手動で$apply
を呼び出さなければならないことについて話しているとき、それは実行時にコールスタック内のAngularJS
から生じないコードについて話しています。
写真で説明する:
スコープ内の参照は、テンプレート内の参照と正確には一致しません。 2つのオブジェクトをデータバインドするとき、最初のオブジェクトをリッスンし、もう一方のオブジェクトを変更する3番目のオブジェクトが必要です。
ここで、<input>
を修正するときは、 data-ref3 をタッチします。そして古典的なデータバインドの仕組みは data-ref4 に変わるでしょう。では、他の{{data}}
式はどのように移動するのでしょうか。
AngularはすべてのバインディングのoldValue
とnewValue
を管理します。そしてすべてのAngular eventの後に、有名な$digest()
ループがWatchListをチェックして何かが変更されたかどうかを確認します。これらのAngularイベントはng-click
、ng-change
、$http
完了です。oldValue
がnewValue
と異なる限り、$digest()
はループします。
前の図では、data-ref1とdata-ref2が変更されています。
卵と鶏のようです。誰が始めたのかは決してわかりませんが、うまくいけば期待通りに動作します。
もう1つのポイントは、メモリとCPUに対する単純なバインディングの影響を簡単に理解できるということです。うまくいけば、デスクトップはこれを処理するのに十分な太さです。携帯電話はそれほど強くはありません。
アタッチされたオブジェクトに変更があるかどうかScope
の定期的なチェックは明らかにありません。スコープにアタッチされているすべてのオブジェクトが監視されるわけではありません。スコープはプロトタイプ的に $$ウォッチャー を管理します。 Scope
は、$$watchers
が呼び出されたときにのみ、この$digest
を反復処理します。
Angularは、これらのそれぞれについて$$ウォッチャーにウォッチャーを追加します。
- {{expression}} - あなたのテンプレートの中(そして式があるところならどこにでも)、またはng-modelを定義するとき。
- $ scope。$ watch( 'expression/function') - あなたのJavaScriptでは見たい角度のスコープオブジェクトを追加することができます。
$ watch functionは3つのパラメータを取ります。
最初のものは単にオブジェクトを返すウォッチャー関数であるか、あるいは単に式を追加することができます。
2つ目は、オブジェクトに変更があったときに呼び出されるリスナー関数です。 DOMの変更のようなものはすべてこの関数に実装されます。
3番目はブール値を取るオプションのパラメータです。 trueの場合、angle deepはオブジェクトを監視します。falseの場合、Angularはオブジェクトを参照しているだけです。 $ 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ディレクティブを使用してハンドラー関数内の$スコープモデルを変更するとします。その場合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は、3つの強力な関数、 $ watch() 、 $ digest() 、および $ apply() の助けを借りて、データバインディングメカニズムを処理します。ほとんどの場合AngularJSは$ scope。$ watch()と$ scope。$ digest()を呼び出しますが、場合によってはこれらの関数を手動で呼び出して新しい値で更新する必要があります。
$ watch() : -
この関数は、$スコープ上の変数の変化を監視するために使用されます。式、リスナー、および等価オブジェクトの3つのパラメーターを受け入れます。リスナーおよび等価オブジェクトはオプションのパラメーターです。
$ digest() -
この関数は、$ scopeオブジェクトとその子の$ scopeオブジェクト内のすべてのウォッチを繰り返し処理します。
(ある場合) $ digest()がウォッチを反復処理するときに、式の値が変更されたかどうかをチェックします。値が変更されている場合、AngularJSは新しい値と古い値でリスナーを呼び出します。 AngularJSが必要と判断したときはいつでも$ digest()関数が呼び出されます。たとえば、ボタンをクリックした後やAJAXを呼び出した後などです。 AngularJSが$ digest()関数を呼び出していない場合があります。その場合はあなた自身でそれを呼ばなければなりません。
$ apply() -
Angular doはAngularJSのコンテキスト内にあるモデルの変更のみを自動的に更新します。 Angularコンテキスト以外のモデル(ブラウザのDOMイベント、setTimeout、XHR、またはサードパーティ製のライブラリなど)で変更を行った場合は、呼び出しによって変更をAngularに通知する必要があります。手動で$ apply()します。 $ apply()関数の呼び出しが終了すると、AngularJSは内部で$ digest()を呼び出します。そのため、すべてのデータバインディングが更新されます。
ある人のデータモデルとフォームをリンクする必要があることが起こりました。私がしたのは、データとフォームを直接マッピングすることでした。
たとえば、モデルに次のようなものがあるとします。
$scope.model.people.name
フォームの制御入力:
<input type="text" name="namePeople" model="model.people.name">
こうすれば、オブジェクトコントローラの値を変更した場合、これは自動的にビューに反映されます。
モデルに合格した例はサーバーデータから更新されます。あなたが郵便番号に基づいて郵便番号を要求し、ロードに基づいてそのビューに関連付けられたコロニーと都市のリストをリストし、デフォルトでユーザーに最初の値を設定します。そしてこれがうまくいった、angularJS
がモデルをリフレッシュするのに時々数秒かかる、それをするためにあなたはデータを表示している間スピナーを置くことができます。
一方向データバインディングは、値がデータモデルから取得され、HTML要素に挿入されるアプローチです。モデルをビューから更新する方法はありません。それは古典的なテンプレートシステムで使われています。これらのシステムはデータを一方向にのみバインドします。
Angular appsでのデータバインディングは、モデルコンポーネントとビューコンポーネント間のデータの自動同期です。
データバインディングを使用すると、モデルをアプリケーション内の単一の真実の源として扱うことができます。ビューは常にモデルの投影です。モデルが変更されると、ビューはその変更を反映します。
AngularJは双方向データバインディングをサポートします。
data View - > Controller&Controller - > Viewにアクセスできることを意味
例
1)
// If $scope have some value in Controller.
$scope.name = "Peter";
// HTML
<div> {{ name }} </div>
O/P
Peter
あなたはng-model
でデータをバインドすることができます。
2)
<input ng-model="name" />
<div> {{ name }} </div>
ここで上の例では、入力ユーザーが何を指定しても、それは<div>
タグに表示されます。
HTMLからの入力をコントローラにバインドしたい場合: -
3)
<form name="myForm" ng-submit="registration()">
<label> Name </lbel>
<input ng-model="name" />
</form>
ここでコントローラで入力name
を使いたいのなら、
$scope.name = {};
$scope.registration = function() {
console.log("You will get the name here ", $scope.name);
};
ng-model
はビューをバインドし、それを式{{ }}
にレンダリングします。ng-model
は、ビュー内でユーザーに表示され、ユーザーが対話するデータです。
したがって、Angular Jにデータをバインドするのは簡単です。
これは、入力フィールドを使用したAngularJSによるデータバインディングの例です。後で説明します
HTMLコード
<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
<input type="text" ng-model="watchInput" Placeholder="type something"/>
<p>{{watchInput}}</p>
</div>
AngularJSコード
myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
//Your Controller code goes here
}]);
上の例でわかるように、 AngularJS はng-model
を使用してHTML要素、特にinput
フィールドで何が起こるかを監視しています。何かが起こったら、何かをしなさい。今回の場合、ng-model
は、口ひげの表記法{{}}
を使用して、ビューにバインドされています。入力フィールド内に入力された内容は即座に画面に表示されます。そしてそれが、最もシンプルな形でAngularJSを使用した、データバインディングの美しさです。
お役に立てれば。
ここで実用的な例を参照してください Codepen
Angular.jsは、ビュー内で作成したすべてのモデルに対してウォッチャーを作成します。モデルが変更されるたびに、 "ng-dirty"クラスがモデルに追加されます。そのため、ウォッチャーは "ng-dirty"クラスを持つすべてのモデルを観察し、コントローラ内の値を更新します。
データバインディング:
データバインディングとは何ですか?
ユーザーがビュー内のデータを変更するたびに、その変更がスコープモデル内で更新されます。その逆も同様です。
どのように可能ですか?
短い答え: ダイジェストサイクルの助けを借りて。
説明: Angular jsスコープモデルにウォッチャーを設定します。モデルに変更があると、リスナー関数が起動されます。
$scope.$watch('modelVar' , function(newValue,oldValue){
//新しい値でDom更新コード
;));
では、ウォッチャ関数はいつ、どのように呼び出されますか。
ウォッチャー関数はダイジェストサイクルの一部として呼び出されます。
ダイジェストサイクルは、ng-model、ng-bind、$ timeout、ng-clickなどのディレクティブ/サービスに組み込まれたangle jsの一部として自動的にトリガーされると呼ばれます。ダイジェストサイクルをトリガーすることができます。
ダイジェストサイクル機能:
$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope
ie$rootScope.$apply()
注:$ apply()は$ rootScopeと同じです$ digest()これは、ダーティーチェックがrootまたはtopまたは親スコープから、angle jsアプリケーション内のすべての子$スコープまでで始まることを意味します。
上記の機能は上記のバージョンのブラウザIEでも動作します。これは、アプリケーションがAngular JSアプリケーションであることを確認することによっても可能です。
ありがとうございました。