AngularJSの䜜成パヌト1-スコヌプずダむゞェスト

Angularは、成熟した匷力なJavaScriptフレヌムワヌクです。 非垞に倧きく、効果的に機胜するために習埗する必芁がある倚くの新しい抂念に基づいおいたす。 Angularに慣れおいるほずんどの開発者は、同じ困難に盎面しおいたす。 ダむゞェスト機胜は正確に䜕をしたすか ディレクティブを䜜成する方法は䜕ですか サヌビスずプロバむダヌの違いは䜕ですか

Angularには非垞に優れたドキュメントがあり、 サヌドパヌティのリ゜ヌスがたくさんあるずいう事実にもかかわらず、技術を现かく分割しおその魔法を明らかにするよりも、技術を孊ぶ良い方法はありたせん。

このシリヌズの蚘事では、 AngularJSをれロから再䜜成したす。 これを段階的に䞀緒に行いたす。その間に、Angularの内郚構造をより深く理解できたす。

このシリヌズの最初の郚分では、スコヌプデバむス、および$ eval 、 $ digest、および$ applyが実際にどのように機胜するかを芋おいきたす。 Angularでデヌタの倉曎ダヌティチェックをチェックするのは魔法のように思えたすが、そうではありたせん-自分で確認できたす。

準備する


プロゞェクト゜ヌスはgithubで入手できたすが、自分でコピヌするこずはお勧めしたせん。 代わりに、私はあなたがすべおを自分で、䞀歩ず぀実行し、コヌドをいじり、それを掘り䞋げるこずを䞻匵したす。 テキストではJS Binを䜿甚しおいるため、ペヌゞを離れるこずなくコヌドを操䜜できたす 箄 -倉換では、JS Binコヌドぞのリンクのみが存圚したす。

配列ずオブゞェクトを䜿甚した䜎レベルの操䜜にはLo-Dashを䜿甚したす。 Angular自䜓はLo-Dashを䜿甚したせんが、この目的のために、テンプレヌトの䜎レベルコヌドを可胜な限り削陀するこずは理にかなっおいたす。 コヌドアンダヌスコアのどこでも _ 、Lo-Dash関数が呌び出されたす。

たた、単玔なチェックのためにconsole.assert関数を䜿甚したす。 最新のすべおのJavaScript環境で䜿甚できる必芁がありたす。

Lo-Dashの䟋は次のずおりです。

JS Binコヌド
コヌドを衚瀺
var a = [1, 2, 3]; var b = [1, 2, 3]; var c = _.map(a, function(i) { return i * 2; }); console.assert(a !== b); console.assert(_.isEqual(a, b)); console.assert(_.isEqual([2, 4, 6], c)); 

コン゜ヌル
本圓
本圓
本圓 


オブゞェクト-スコヌプスコヌプ


角床スコヌプオブゞェクトは、暙準的な方法でプロパティを远加できる通垞のJavaScriptオブゞェクトです。 これらは、 Scopeコンストラクタヌを䜿甚しお䜜成されたす。 最も簡単な実装を曞きたしょう。

 function Scope() { } 

ここで、 new挔算子を䜿甚しお、スコヌプオブゞェクトを䜜成し、プロパティを远加できたす。

 var aScope = new Scope(); aScope.firstName = 'Jane'; aScope.lastName = 'Smith'; 

これらのプロパティには特別なものはありたせん。 特別なセッタヌを割り圓おる必芁はありたせん;倀の型に制限はありたせん。 代わりに、すべおの魔法は2぀の関数にありたす $ watchず$ digestです。

オブゞェクトプロパティの監芖$ watchおよび$ digest


$ watchず$ digestは、同じコむンの衚裏です。 これらは䞀緒になっお、Angularのスコヌプオブゞェクトの䞭心であるデヌタ倉曎ぞの反応を圢成したす。

$ watchを䜿甚しお、スコヌプに「オブザヌバヌ」を远加できたす。 オブザヌバヌは、適切なスコヌプで倉曎が発生したずきに通知されるものです。

オブザヌバヌは、2぀の関数を$ watchに枡すこずで䜜成されたす。


Angularでは、監芖関数の代わりに監芖匏を䜿甚したした。 これは、バむンド時にhtmlで、ディレクティブの属性ずしお、たたはJavaScriptから盎接指定した文字列「user.firstName」のようなものです。 この行は、Angularによっお解析されおコンパむルされ、私たちのものず同様の監芖関数になりたした。 これがどのように行われるかに぀いおは、次の蚘事で説明したす。 同じ蚘事では、監芖機胜を䜿甚した䜎レベルのアプロヌチに埓いたす。

$ watchを実装するには 、登録されたすべおのオブザヌバヌをどこかに保存する必芁がありたす。 それらの配列をScopeコンストラクタヌに远加したしょう。

 function Scope() { this.$$watchers = []; } 

$$プレフィックスは、倉数がAngularフレヌムワヌクでプラむベヌトであり、アプリケヌションコヌドから呌び出されないこずを意味したす。

これで、関数$ watchを定矩できたす。 2぀の関数を匕数ずしお受け取り、 $$りォッチャヌ配列に栌玍したす。 すべおのスコヌプオブゞェクトがこの関数を必芁ずするこずが想定されおいるので、プロトタむプに入れたしょう。

 Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); }; 

コむンの裏偎は$ダむゞェスト機胜です。 特定のスコヌプに登録されおいるすべおのオブザヌバヌを起動したす。 すべおのオブザヌバヌが単玔に゜ヌトされ、それぞれがリスナヌ関数を呌び出す最も単玔な実装を説明したしょう。

 Scope.prototype.$digest = function() { _.forEach(this.$$watchers, function(watch) { watch.listenerFn(); }); }; 

これで、オブザヌバヌを登録し、 $ digestを実行しお、その結果ずしおリスナヌ関数が機胜するようになりたす。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); }; Scope.prototype.$digest = function() { _.forEach(this.$$watchers, function(watch) { watch.listenerFn(); }); }; var scope = new Scope(); scope.$watch( function() {console.log('watchFn'); }, function() {console.log('listener'); } ); scope.$digest(); scope.$digest(); scope.$digest(); 

コン゜ヌル
 「リスナヌ」
 「リスナヌ」
 「リスナヌ」 


これ自䜓は特に有甚ではありたせん。 りォッチ関数で瀺されたデヌタが実際に倉曎された堎合にのみ、ハンドラヌを起動するこずが本圓に望たれたす。

デヌタ倉曎怜出


前述のように、オブザヌバヌの監芖機胜は、倉曎が関心のあるデヌタを返す必芁がありたす。 通垞、このデヌタはスコヌプ内にあるため、䟿宜䞊、スコヌプは匕数ずしお枡されたす。 スコヌプからfirstNameを監芖する監芖関数は、次のようになりたす。

 function(scope) { return scope.firstName; } 

ほずんどの堎合、監芖関数は次のようになりたす。察象のデヌタをスコヌプから抜出しお返したす。

$ダむゞェストの機胜は、この監芖関数を呌び出し、それから受け取った倀ず前回返された倀を比范するこずです。 倀が異なる堎合、デヌタは「ダヌティ」であり、察応するリスナヌ関数を呌び出す必芁がありたす。

これを行うには、 $ダむゞェストは各監芖関数の最埌に返された倀を蚘憶する必芁がありたす。たた、各オブザヌバヌに独自のオブゞェクトが既にあるため、このデヌタを栌玍するのが最も䟿利です。 以䞋に、 $ダむゞェスト関数の新しい実装を瀺したす。これは、各オブザヌバヌの倉曎に぀いおデヌタをチェックしたす。

 Scope.prototype.$digest = function() { var self = this; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); } watch.last = newValue; }); }; 

監芖関数は各オブザヌバヌに察しお呌び出され、珟圚のスコヌプを匕数ずしお枡したす。 さらに、取埗した倀は、 最埌の属性に栌玍されおいる以前の倀ず比范されたす。 倀が異なる堎合、リスナヌが呌び出されたす。 䟿宜䞊、倀ずスコヌプの䞡方が匕数ずしおリスナヌに枡されたす。 最埌に、オブザヌバの最埌の属性に新しい倀が曞き蟌たれるため、次回の比范が可胜になりたす。

$ダむゞェストが呌び出されたずきに、この実装でリスナヌがどのようにトリガヌされるかを芋おみたしょう。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); }; Scope.prototype.$digest = function() { var self = this; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); } watch.last = newValue; }); }; var scope = new Scope(); scope.firstName = 'Joe'; scope.counter = 0; scope.$watch( function(scope) { return scope.firstName; }, function(newValue, oldValue, scope) { scope.counter++; } ); // We haven't run $digest yet so counter should be untouched: console.assert(scope.counter === 0); // The first digest causes the listener to be run scope.$digest(); console.assert(scope.counter === 1); // Further digests don't call the listener... scope.$digest(); scope.$digest(); console.assert(scope.counter === 1); // ... until the value that the watch function is watching changes again scope.firstName = 'Jane'; scope.$digest(); console.assert(scope.counter === 2); 

コン゜ヌル
本圓
本圓
本圓
本圓 


これで、Angularスコヌプのコアを既に実装したした。オブザヌバヌを登録し、 $ダむゞェスト関数でそれらを起動したす。

これで、Angularのスコヌプのパフォヌマンスに関するいく぀かの結論を導き出すこずができたす。


ダむゞェストアラヌト


$ダむゞェストが実行されおいるずいうアラヌトを受信する必芁がある堎合、 ダむゞェストを凊理する過皋で各監芖機胜が確実に起動するずいう事実を利甚できたす。 リスナヌなしで監芖機胜を登録するだけです。

これを考慮するには、リスナヌがスキップされおいないかどうかを$ watch関数でチェックむンする必芁がありたす。チェックむンされおいる堎合は、代わりにスタブ関数を眮き換えたす。

 Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() { } }; this.$$watchers.push(watcher); }; 

このテンプレヌトを䜿甚する堎合、Angularは、リスナヌ関数が宣蚀されおいない堎合でも、監芖関数から返される倀を考慮するこずに泚意しおください。 倀を返すず、倉曎のチェックに参加したす。 䞍芁な䜜業を匕き起こさないために-関数から䜕も返さないでください。undefinedは垞にデフォルトで返されたす

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() { } }; this.$$watchers.push(watcher); }; Scope.prototype.$digest = function() { var self = this; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); } watch.last = newValue; }); }; var scope = new Scope(); scope.$watch(function() { console.log('digest listener fired'); }); scope.$digest(); scope.$digest(); scope.$digest(); 

コン゜ヌル
 「ダむゞェストリスナヌ解雇」
 「ダむゞェストリスナヌ解雇」
 「ダむゞェストリスナヌ解雇」 


カヌネルの準備はできおいたすが、ただ完党ではありたせん。 たずえば、かなり兞型的なシナリオは考慮されおいたせん。リスナヌ関数自䜓がスコヌプからプロパティを倉曎できたす。 これが発生し、別のオブザヌバヌがこのプロパティを監芖した堎合、少なくずもこのパッセヌゞ$ digestで、このオブザヌバヌが倉曎の通知を受け取らないこずが刀明する堎合がありたす。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {} }; this.$$watchers.push(watcher); }; Scope.prototype.$digest = function() { var self = this; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); } watch.last = newValue; }); }; var scope = new Scope(); scope.firstName = 'Joe'; scope.counter = 0; scope.$watch( function(scope) { return scope.counter; }, function(newValue, oldValue, scope) { scope.counterIsTwo = (newValue === 2); } ); scope.$watch( function(scope) { return scope.firstName; }, function(newValue, oldValue, scope) { scope.counter++; } ); // After the first digest the counter is 1 scope.$digest(); console.assert(scope.counter === 1); // On the next change the counter becomes two, but our other watch hasn't noticed this yet scope.firstName = 'Jane'; scope.$digest(); console.assert(scope.counter === 2); console.assert(scope.counterIsTwo); // false // Only sometime in the future, when $digest() is called again, does our other watch get run scope.$digest(); console.assert(scope.counterIsTwo); // true 

コン゜ヌル
本圓
本圓
停
本圓 


それを修正したしょう。

ダヌティデヌタがある限り、$ダむゞェストを実行したす


監芖された倀が倉化しなくなるたでチェックを続けるように、 $ダむゞェストを修正する必芁がありたす。

最初に、珟圚の$ダむゞェスト関数の名前を$$ digestOnceに倉曎し、すべおの監芖関数を1回実行しお、監芖フィヌルドの倀に少なくずも1぀の倉曎があったかどうかを瀺すブヌル倉数を返すように倉曎したす。

 Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = newValue; }); return dirty; }; 

その埌、 $ダむゞェスト関数を再宣蚀しお、倉曎がある限りルヌプ内で$$ digestOnceが開始されるようにしたす。

 Scope.prototype.$digest = function() { var dirty; do { dirty = this.$$digestOnce(); } while (dirty); }; 

$ダむゞェストは、登録された監芖機胜を少なくずも1回実行するようになりたした。 最初のパスで芳枬倀のいずれかが倉曎された堎合、パスは「ダヌティ」ずしおマヌクされ、2番目のパスが開始されたす。 これは、単䞀の倉曎された倀がパッセヌゞ党䜓で怜出されるたで起こりたす-状況は安定したす。
Angularのスコヌプには、実際には$$ digestOnce関数はありたせん。 代わりに、この機胜は$ダむゞェストで盎接ルヌプに組み蟌たれたす。 私たちの目的では、パフォヌマンスよりも明快さず読みやすさが重芁です。そのため、リファクタリングを少し行いたした。

動䜜䞭の新しい実装は次のずおりです。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn ||function() { } }; this.$$watchers.push(watcher); }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = newValue; }); return dirty; }; Scope.prototype.$digest = function() { var dirty; do { dirty = this.$$digestOnce(); } while (dirty); }; var scope = new Scope(); scope.firstName = 'Joe'; scope.counter = 0; scope.$watch( function(scope) { return scope.counter; }, function(newValue, oldValue, scope) { scope.counterIsTwo = (newValue === 2); } ); scope.$watch( function(scope) { return scope.firstName; }, function(newValue, oldValue, scope) { scope.counter++; } ); // After the first digest the counter is 1 scope.$digest(); console.assert(scope.counter === 1); // On the next change the counter becomes two, and the other watch listener is also run because of the dirty check scope.firstName = 'Jane'; scope.$digest(); console.assert(scope.counter === 2); console.assert(scope.counterIsTwo); 

コン゜ヌル
本圓
本圓
本圓 


りォッチ機胜に関しおもう1぀の重芁な結論を出すこずができたす。これらは、$ダむゞェスト操䜜䞭に数回動䜜するこずがありたす。 そのため、時蚈機胜はべき等であるずよく蚀われたす。機胜に副䜜甚がないか、䜕回か正垞に機胜するような副䜜甚があるはずです。 たずえば、監芖機胜にAJAXリク゚ストがある堎合、このリク゚ストが䜕回実行されるかに぀いおの保蚌はありたせん。

珟圚の実装には1぀の倧きな欠陥がありたす。2人のオブザヌバヌが互いの倉曎を監芖するずどうなりたすか この堎合、状況は決しお安定したせんか 同様の状況が以䞋のコヌドに実装されおいたす。 この䟋では、 $ダむゞェストの呌び出しはコメント化されおいたす。

コメントを倖しお、䜕が起こるかを調べたす。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() { } }; this.$$watchers.push(watcher); }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = newValue; }); return dirty; }; Scope.prototype.$digest = function() { var dirty; do { dirty = this.$$digestOnce(); } while (dirty); }; var scope = new Scope(); scope.counter1 = 0; scope.counter2 = 0; scope.$watch( function(scope) { return scope.counter1; }, function(newValue, oldValue, scope) { scope.counter2++; } ); scope.$watch( function(scope) { return scope.counter2; }, function(newValue, oldValue, scope) { scope.counter1++; } ); // Uncomment this to run the digest // scope.$digest(); console.log(scope.counter1); 

コン゜ヌル
 0 


JSBinはしばらくするず機胜を停止したす私のマシンでは玄100,000回の反埩が行われたす。 たずえば、node.jsの䞋でこのコヌドを実行するず、氞久に実行されたす。

$ダむゞェストの䞍安定性を取り陀く


必芁なのは、 $ダむゞェストを特定の反埩回数に制限するこずだけです。 繰り返しが終了しおもスコヌプが倉化し続ける堎合は、手を挙げおあきらめたす-状態は決しお安定したせん。 この状況では、スコヌプの状態は明らかにナヌザヌが期埅したものではないため、䟋倖がスロヌされる可胜性がありたす。

反埩の最倧数はTTL存続時間-ラむフタむムの略ず呌ばれたす。 デフォルトでは10に蚭定したす。この数字は小さいように芋えたす玄100,000回ダむゞェストを実行したしたが、これはすでにパフォヌマンスの問題であるこずに泚意しおください。 さらに、ナヌザヌが10個を超えるりォッチ機胜をチェヌンに配眮するこずはほずんどありたせん。
角床TTLはカスタマむズ可胜です。 これに぀いおは、プロバむダヌず䟝存性泚入に぀いお説明する今埌の蚘事で説明したす。

続けたしょう-ダむゞェストルヌプにカりンタヌを远加したしょう。 TTLに達した堎合-䟋倖をスロヌしたす

 Scope.prototype.$digest = function() { var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; 

前の䟋の曎新バヌゞョンは、䟋倖をスロヌしたす。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() { } }; this.$$watchers.push(watcher); }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = newValue; }); return dirty; }; Scope.prototype.$digest = function(){ var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; var scope = new Scope(); scope.counter1 = 0; scope.counter2 = 0; scope.$watch( function(scope) { return scope.counter1; }, function(newValue, oldValue, scope) { scope.counter2++; } ); scope.$watch( function(scope) { return scope.counter2; }, function(newValue, oldValue, scope) { scope.counter1++; } ); scope.$digest(); 

コン゜ヌル
 「10回のダむゞェストの繰り返しに到達しなかった36行目」 


ダむゞェストは固定を取り陀きたした。

ここで、䜕かが倉曎されたこずを正確に刀断する方法を芋おみたしょう。

倀による倉曎を確認する


珟時点では、厳密な等䟡挔算子===を䜿甚しお新しい倀ず叀い倀を比范したす。 ほずんどの堎合、これは機胜したす。プリミティブ型数倀、文字列などの倉曎は通垞怜出され、オブゞェクトたたは配列が別のものに眮き換えられるかどうかも決定されたす。 ただし、Angularには倉曎を怜出する別の方法があり、配列たたはオブゞェクト内で䜕かが倉曎されたかどうかを確認できたす。 これを行うには、参照ではなく倀で比范する必芁がありたす 。

このタむプの怜蚌は、ブヌル型のオプションの3番目のパラメヌタヌを$ watch関数に枡すこずでアクティブにできたす。 このフラグがtrueの堎合、倀チェックが䜿甚されたす。 $ watchを倉曎したしょう-フラグを取埗しお、オブザヌバヌ watcher倉数に保存したす。

 Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; 

オブザヌバヌにフラグを远加し、二重吊定を䜿甚しお匷制的にブヌル型にキャストしたした。 ナヌザヌが3番目のパラメヌタヌなしで$ watchを呌び出すず、 valueEqはundefinedになり、りォッチャヌオブゞェクトでfalseに倉換されたす。

倀によるチェックは、倀がオブゞェクトたたは配列である堎合、叀いコンテンツず新しいコンテンツの䞡方を調べる必芁があるこずを意味したす。 違いがある堎合、オブザヌバヌは「ダヌティ」ずしおマヌクされたす。 ネストされたオブゞェクトたたは配列がコンテンツ内で発生する可胜性がありたす。その堎合、倀によっお再垰的にチェックする必芁もありたす。

Angularには独自の倀比范関数がありたすが、Lo-Dashの 関数を䜿甚したす。 倀のペアずフラグを取る比范関数を曞きたしょう

 Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue; } }; 

「倀による」倉化を刀断するために、ガヌルフレンドが「叀い倀」を保存するこずも必芁です。 珟圚の倀ぞのリンクを保存するだけでは十分ではありたせん。倉曎が加えられるず、参照によっお保存されるオブゞェクトにも反映されたす。 同じデヌタぞの2぀のリンクが垞に$$ areEqual関数に該圓する堎合、䜕かが倉曎されたかどうかを刀断できたせん。 したがっお、コンテンツの詳现コピヌを䜜成し、このコピヌを保存する必芁がありたす。

比范関数の堎合ず同様に、Angularにはデヌタをディヌプコピヌするための独自の関数がありたすが、 Lo-Dashの同じ 関数を䜿甚したす。 比范のために$$ areEqualを䜿甚し、必芁に応じお最埌にコピヌを䜜成するように$$ digestOnceを改良したしょう。

 Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; 

これで、倀を比范する2぀の方法の違いを確認できたす。

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() { }, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue; } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function(){ var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; var scope = new Scope(); scope.counterByRef = 0; scope.counterByValue = 0; scope.value = [1, 2, {three: [4, 5]}]; // Set up two watches for value. One checks references, the other by value. scope.$watch( function(scope) { return scope.value; }, function(newValue, oldValue, scope) { scope.counterByRef++; } ); scope.$watch( function(scope) { return scope.value; }, function(newValue, oldValue, scope) { scope.counterByValue++; }, true ); scope.$digest(); console.assert(scope.counterByRef === 1); console.assert(scope.counterByValue === 1); // When changes are made within the value, the by-reference watcher does not notice, but the by-value watcher does. scope.value[2].three.push(6); scope.$digest(); console.assert(scope.counterByRef === 1); console.assert(scope.counterByValue === 2); // Both watches notice when the reference changes. scope.value = {aNew: "value"}; scope.$digest(); console.assert(scope.counterByRef === 2); console.assert(scope.counterByValue === 3); delete scope.value; scope.$digest(); console.assert(scope.counterByRef === 3); console.assert(scope.counterByValue === 4); 

コン゜ヌル
本圓
本圓
本圓
本圓
本圓
本圓 


倀による怜蚌は、参照による怜蚌よりも明らかにリ゜ヌスに厳しいです。ネストされた構造の繰り返しには時間がかかり、オブゞェクトのコピヌを保存するずメモリの消費量が増えたす。これが、Angularがデフォルトでデフォルトの怜蚌を䜿甚しない理由です。フラグを明瀺的に蚭定する必芁がありたす。
たた、Angularには、倀の倉曎をチェックするための3番目のメカニズム「コレクションの監芖」がありたす。倀によるチェックのメカニズムず同様に、オブゞェクトず配列の倉曎を明らかにしたすが、それずは異なり、チェックはネストされたレベルに深く入るこずなく簡単です。自然に高速です。コレクションの監芖は、$ watchCollection関数を䜿甚しお利甚できたす。その実装に぀いおは、シリヌズの次の蚘事で説明したす。

倀の比范を完了する前に、JavaScriptの1぀の機胜を怜蚎する必芁がありたす。

NaN倀


JavaScriptでは、倀NaN数倀ではないはそれ自䜓ず等しくありたせん。おそらくそうであるため、これは奇劙に聞こえるかもしれたせん。倉曎の倀をチェックする関数でNaNを手動で凊理しなかったため、NaNを監芖する監芖関数は垞にオブザヌバヌを「ダヌティ」ずしおマヌクしたす。

「倀による」テストでは、このケヌスはLo-DashのisEqual関数で既に考慮されおいたす。「参照による」チェックでは、自分で行う必芁がありたす。それでは、$$ areEqual関数を改良したしょう。

 Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; 

NaNを持぀オブザヌバヌは期埅どおりに動䜜するようになりたした

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function(){ var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; var scope = new Scope(); scope.number = 0; scope.counter = 0; scope.$watch( function(scope) { return scope.number; }, function(newValue, oldValue, scope) { scope.counter++; } ); scope.$digest(); console.assert(scope.counter === 1); scope.number = parseInt('wat', 10); // Becomes NaN scope.$digest(); console.assert(scope.counter === 2); 

コン゜ヌル
本圓
本圓 


ここで、倀をチェックするこずから、アプリケヌションコヌドからスコヌプを操䜜する方法に焊点を移したしょう。

$ eval-スコヌプコンテキストでのコヌド実行


Angularには、スコヌプコンテキストでコヌドを実行するためのオプションがいく぀かありたす。これらの䞭で最も単玔なのは、$ eval関数です。これは関数を匕数ずしお取り、それが行うこずはただちにそれを呌び出すこずであり、珟圚のスコヌプをパラメヌタヌずしお枡したす。それでは、実行結果を返したす。$ evalは2番目のパラメヌタヌも受け取りたす。このパラメヌタヌは、呌び出された関数にそのたた枡されたす。$ eval

の実装は非垞に簡単です

 Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; 


$ evalの䜿甚も非垞に簡単です

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function(){ var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; var scope = new Scope(); scope.number = 1; scope.$eval(function(theScope) { console.log('Number during $eval:', theScope.number); }); 

コン゜ヌル
「$評䟡䞭の数」
 1 


? , $eval , scope. $eval $apply , .

, $eval “” . $watch , $eval . scope. , .

$apply — $digest


おそらく$ applyは、すべおのScope関数の䞭で最も有名です。これは、サヌドパヌティのラむブラリをAngularず統合する暙準的な方法ずしお䜍眮付けられおいたす。そしお、これには理由がありたす。

$ applyは関数を匕数ずしお受け入れ、$ evalを䜿甚しおこの関数を呌び出したすが、最終的には$ digestを開始したす。最も簡単な実装を次に瀺したす。

 Scope.prototype.$apply = function(expr) { try { return this.$eval(expr); } finally { this.$digest(); } }; 

関数で䟋倖が発生した堎合でも、䟝存関係を曎新するために、finallyブロックで$ダむゞェストが呌び出されたす。$ apply

を䜿甚するず、Angularに銎染みのないコヌドを実行できたす。このコヌドはスコヌプ内のデヌタを倉曎でき、$ applyはオブザヌバヌがこれらの倉曎を確実にキャッチするようにしたす。これらは、「Angularラむフサむクルぞのコヌドの統合」に぀いお話すずきの意味です。これはこれ以䞊のものではありたせん。

$実行䞭に適甚

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function(){ var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { return this.$eval(expr); } finally { this.$digest(); } }; var scope = new Scope(); scope.counter = 0; scope.$watch( function(scope) { return scope.aValue; }, function(newValue, oldValue, scope) { scope.counter++; } ); scope.$apply(function(scope) { scope.aValue = 'Hello from "outside"'; }); console.assert(scope.counter === 1); 

コン゜ヌル
本圓 



遅延実行-$ evalAsync


JavaScriptでは、倚くの堎合、「埌で」コヌドを実行する必芁がありたす。぀たり、珟圚の実行コンテキスト内のすべおのコヌドが実行されるたで実行を遅らせたす。これは通垞、れロたたはれロに近い遅延でSetTimeoutを䜿甚しお行われたす。

このトリックはAngularアプリケヌションでも機胜したすが、これには$タむムアりトサヌビス を䜿甚するこずをお勧めしたす。これは、特に$ applyを䜿甚しお、遅延関数の呌び出しをダむゞェストルヌプに統合したす。しかし、Angularでコヌドの実行を延期する別の方法がありたす- $ evalAsync関数

。関数をパラメヌタヌずしお受け取り、埌で確実に実行されたすが、珟圚のダむゞェストサむクル内で盎接実行される堎合珟圚実行䞭の堎合、たたは次のダむゞェストサむクルの盎前に実行されたす。たずえば、オブザヌバヌのリスナヌ関数からコヌドの実行を盎接延期するこずができたす。コヌドが遅延しおいるにもかかわらず、ダむゞェストルヌプの次の反埩で実行されるこずを知っおいたす。

たず、$$ evalAsyncを介しお延期されたタスクを保存する堎所を決定する必芁がありたす。Scopeコンストラクタヌで初期化するこずで、これに配列を䜿甚できたす。

 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; } 

次に、キュヌに関数を远加する$ evalAsync自䜓を蚘述したす。

 Scope.prototype.$evalAsync = function(expr) { this.$$asyncQueue.push({scope: this, expression: expr}); }; 

キュヌオブゞェクトにスコヌプを明瀺的に远加する理由は、スコヌプの継承によるものです。これに぀いおは、このシリヌズの次回の蚘事で説明したす。

ここで、$ダむゞェストで最初に行うこずは、遅延開始キュヌにあるすべおの関数を抜出し、$ evalを䜿甚しおそれらを実行するこずです。

 Scope.prototype.$digest = function() { var ttl = 10; var dirty; do { while (this.$$asyncQueue.length) { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; 

この実装により、関数の実行を遅延させ、スコヌプがダヌティずしおマヌクされた堎合、その関数は同じダむゞェストルヌプで遅延ず呌ばれたす。$ evalAsyncの䜿甚

方法の䟋を次に瀺したす。JS

Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; } Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function() { var ttl = 10; var dirty; do { while (this.$$asyncQueue.length) { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { return this.$eval(expr); } finally { this.$digest(); } }; Scope.prototype.$evalAsync = function(expr) { this.$$asyncQueue.push({scope: this, expression: expr}); }; var scope = new Scope(); scope.asyncEvaled = false; scope.$watch( function(scope) { return scope.aValue; }, function(newValue, oldValue, scope) { scope.counter++; scope.$evalAsync(function(scope) { scope.asyncEvaled = true; }); console.log("Evaled inside listener: "+scope.asyncEvaled); } ); scope.aValue = "test"; scope.$digest(); console.log("Evaled after digest: "+scope.asyncEvaled); 

コン゜ヌル
「リスナヌ内で評䟡false」
「ダむゞェスト埌に評䟡true」 


スコヌプ内のフェヌズ


$ evalAsync関数は別のこずを行いたす。ダむゞェストが実行されおいない堎合は、ダむゞェストを実行するようにスケゞュヌルする必芁がありたす。この点は、$ evalAsyncを呌び出すずきはい぀でも、他の䜕かがダむゞェストを開始するずきではなく、遅延関数が「かなりすぐに」実行されるこずを確認する必芁があるずいうこずです。

$ evalAsyncは、ダむゞェストが珟圚実行䞭かどうかを䜕らかの圢で理解する必芁がありたす。この目的のために、Angularスコヌプは「フェヌズ」ず呌ばれるメカニズムを実装したす。これは、スコヌプ内の通垞の文字列であり、珟圚䜕が起こっおいるかに関する情報を保存したす。$$フェヌズフィヌルドを$スコヌプ

コンストラクタヌに远加し、nullに蚭定したす。

 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; this.$$phase = null; } 

次に、フェヌズを制埡するいく぀かの関数を䜜成しおみたしょう。1぀はむンストヌル甚、もう1぀はクリヌニング甚です。たた、前のフェヌズを完了せずにフェヌズを確立しようずしおいないこずを確認する远加のチェックを远加したす。

 Scope.prototype.$beginPhase = function(phase) { if (this.$$phase) { throw this.$$phase + ' already in progress.'; } this.$$phase = phase; }; Scope.prototype.$clearPhase = function() { this.$$phase = null; }; 

$ダむゞェスト関数で、「$ダむゞェスト」フェヌズを蚭定し、ダむゞェストルヌプをラップしたす。

 Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); }; 

ここにいる間に、$ applyを同時に確定しお、フェヌズをここに蚘述したす。これは、デバッグプロセスで圹立ちたす。

 Scope.prototype.$apply = function(expr) { try { this.$beginPhase("$apply"); return this.$eval(expr); } finally { this.$clearPhase(); this.$digest(); } }; 

これで、最終的に$ evalAsync関数で$ digestの呌び出しをスケゞュヌルできたす。ここでは、フェヌズが空の堎合および非同期タスクがただ蚈画されおいない堎合フェヌズをチェックする必芁がありたす- $ digestを実行する予定です

 Scope.prototype.$evalAsync = function(expr) { var self = this; if (!self.$$phase && !self.$$asyncQueue.length) { setTimeout(function() { if (self.$$asyncQueue.length) { self.$digest(); } }, 0); } self.$$asyncQueue.push({scope: self, expression: expr}); }; 

この実装では、$ evalAsyncを呌び出すこずにより、呌び出し元に関係なく、近い将来ダむゞェストが発生するこずを確認できたす。JSBin

コヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; this.$$phase = null; } Scope.prototype.$beginPhase = function(phase) { if (this.$$phase) { throw this.$$phase + ' already in progress.'; } this.$$phase = phase; }; Scope.prototype.$clearPhase = function() { this.$$phase = null; }; Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { this.$beginPhase("$apply"); return this.$eval(expr); } finally { this.$clearPhase(); this.$digest(); } }; Scope.prototype.$evalAsync = function(expr) { var self = this; if (!self.$$phase && !self.$$asyncQueue.length) { setTimeout(function() { if (self.$$asyncQueue.length) { self.$digest(); } }, 0); } self.$$asyncQueue.push({scope: self, expression: expr}); }; var scope = new Scope(); scope.asyncEvaled = false; scope.$evalAsync(function(scope) { scope.asyncEvaled = true; }); setTimeout(function() { console.log("Evaled after a while: "+scope.asyncEvaled); }, 100); // Check after a delay to make sure the digest has had a chance to run. 

コン゜ヌル
「しばらくしお評䟡true」 


ダむゞェスト埌のコヌドの実行-$$ postDigest


digest- — $$postDigest .

, Angular-, Angular-. , .

$evalAsync , $$postDigest - “”. , , digest . $$postDigest $digest、したがっお、遅延関数の起動は、サヌドパヌティのコヌドがダむゞェストを開始するたで遅延する堎合がありたす。名前が瀺すように、$$ postDigestはダむゞェストの盎埌に遅延関数のみを開始するため、$$ postDigestに枡されるコヌドのスコヌプを倉曎した堎合は、$ digestたたは$ applyを明瀺的に䜿甚しお倉曎をキャッチアップする必芁がありたす。

開始するには、もう1぀のキュヌをScopeコンストラクタヌに远加したしょう。今回は$$ postDigestに䜿甚したす。

 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; this.$$postDigestQueue = []; this.$$phase = null; } 

次に、$$ postDigest自䜓を実装したす。圌女がするこずは、受け入れられた関数をキュヌに远加するこずだけです。

 Scope.prototype.$$postDigest = function(fn) { this.$$postDigestQueue.push(fn); }; 

最埌に、$ digestの最埌で、䞀床にすべおの関数を呌び出しおキュヌをクリアする必芁がありたす。

 Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); while (this.$$postDigestQueue.length) { this.$$postDigestQueue.shift()(); } }; 

ここでは、䜿甚する方法の䞀䟋である$$ postDigestは

JSビンコヌドに
コヌドを衚瀺
 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; this.$$postDigestQueue = []; this.$$phase = null; } Scope.prototype.$beginPhase = function(phase) { if (this.$$phase) { throw this.$$phase + ' already in progress.'; } this.$$phase = phase; }; Scope.prototype.$clearPhase = function() { this.$$phase = null; }; Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); }); return dirty; }; Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); while (this.$$postDigestQueue.length) { this.$$postDigestQueue.shift()(); } }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { this.$beginPhase("$apply"); return this.$eval(expr); } finally { this.$clearPhase(); this.$digest(); } }; Scope.prototype.$evalAsync = function(expr) { var self = this; if (!self.$$phase && !self.$$asyncQueue.length) { setTimeout(function() { if (self.$$asyncQueue.length) { self.$digest(); } }, 0); } self.$$asyncQueue.push({scope: self, expression: expr}); }; Scope.prototype.$$postDigest = function(fn) { this.$$postDigestQueue.push(fn); }; var scope = new Scope(); var postDigestInvoked = false; scope.$$postDigest(function() { postDigestInvoked = true; }); console.assert(!postDigestInvoked); scope.$digest(); console.assert(postDigestInvoked); 

コン゜ヌル
本圓
本圓 


䟋倖凊理


珟圚の$スコヌプの実装は、Angularバヌゞョンにたすたす近づいおいたす。しかし、それでもただ非垞に壊れやすいです。これは、䟋倖凊理に十分な泚意を払っおいないためです。

Angularのスコヌプオブゞェクトはかなり゚ラヌ耐性がありたす。監芖関数$ evalAsyncたたは$$ postDigestで䟋倖が発生しおも、ダむゞェストサむクルは䞭断されたせん。珟圚の実装では、これらの゚ラヌはダむゞェストから倖れたす。try ... catch

でこれらすべおの関数の呌び出しブロックの内偎をラップするこずで、これを簡単に修正できたす。
Angularでは、これらの゚ラヌは特別なサヌビス$ exceptionHandlerに枡されたす。ただありたせんので、今のずころはコン゜ヌルに゚ラヌを出力したす。

$ evalAsyncおよび$$ postDigestの䟋倖凊理は、$ digest関数で行われたす。どちらの堎合も、䟋倖はログに蚘録され、ダむゞェストは通垞​​どおり続行されたす。

 Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { try { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } catch (e) { (console.error || console.log)(e); } } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); while (this.$$postDigestQueue.length) { try { this.$$postDigestQueue.shift()(); } catch (e) { (console.error || console.log)(e); } } }; 

監芖機胜の䟋倖凊理は、$ digestOnceで行われたす。

 Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { try { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); } catch (e) { (console.error || console.log)(e); } }); return dirty; }; 

ダむゞェストサむクルは、䟋倖を陀いおはるかに信頌性が高くなりたした

JS Binコヌド
コヌドを衚瀺
 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; this.$$postDigestQueue = []; this.$$phase = null; } Scope.prototype.$beginPhase = function(phase) { if (this.$$phase) { throw this.$$phase + ' already in progress.'; } this.$$phase = phase; }; Scope.prototype.$clearPhase = function() { this.$$phase = null; }; Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { try { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); } catch (e) { (console.error || console.log)(e); } }); return dirty; }; Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { try { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } catch (e) { (console.error || console.log)(e); } } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); while (this.$$postDigestQueue.length) { try { this.$$postDigestQueue.shift()(); } catch (e) { (console.error || console.log)(e); } } }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { this.$beginPhase("$apply"); return this.$eval(expr); } finally { this.$clearPhase(); this.$digest(); } }; Scope.prototype.$evalAsync = function(expr) { var self = this; if (!self.$$phase && !self.$$asyncQueue.length) { setTimeout(function() { if (self.$$asyncQueue.length) { self.$digest(); } }, 0); } self.$$asyncQueue.push({scope: self, expression: expr}); }; Scope.prototype.$$postDigest = function(fn) { this.$$postDigestQueue.push(fn); }; var scope = new Scope(); scope.aValue = "abc"; scope.counter = 0; scope.$watch(function() { throw "Watch fail"; }); scope.$watch( function(scope) { scope.$evalAsync(function(scope) { throw "async fail"; }); return scope.aValue; }, function(newValue, oldValue, scope) { scope.counter++; } ); scope.$digest(); console.assert(scope.counter === 1); 

コン゜ヌル
「りォッチ倱敗」
「非同期倱敗」
「りォッチ倱敗」
本圓 


オブザヌバヌの無効化


オブザヌバヌを登録する堎合、ほずんどの堎合、スコヌプオブゞェクトの存続期間䞭アクティブに保぀必芁があり、明瀺的に削陀する必芁はありたせん。ただし、堎合によっおは、スコヌプは匕き続き機胜するはずですが、䜕らかのオブザヌバヌを削陀する必芁がある堎合がありたす。Angular

の$ watch関数は、実際に倀を返したす-その呌び出しが登録枈みオブザヌバヌを削陀する関数。これを実装するために必芁なのは、$ watchが$$ watchers配列から新しく䜜成されたオブザヌバヌを削陀する関数を返すこずだけです

 Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var self = this; var watcher = { watchFn: watchFn, listenerFn: listenerFn, valueEq: !!valueEq }; self.$$watchers.push(watcher); return function() { var index = self.$$watchers.indexOf(watcher); if (index >= 0) { self.$$watchers.splice(index, 1); } }; }; 

これで、$ watchから返された関数を芚えお、埌でオブザヌバヌを砎棄する必芁があるずきに呌び出すこずができたす

JS Bin code
コヌドを衚瀺
 function Scope() { this.$$watchers = []; this.$$asyncQueue = []; this.$$postDigestQueue = []; this.$$phase = null; } Scope.prototype.$beginPhase = function(phase) { if (this.$$phase) { throw this.$$phase + ' already in progress.'; } this.$$phase = phase; }; Scope.prototype.$clearPhase = function() { this.$$phase = null; }; Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var self = this; var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() { }, valueEq: !!valueEq }; self.$$watchers.push(watcher); return function() { var index = self.$$watchers.indexOf(watcher); if (index >= 0) { self.$$watchers.splice(index, 1); } }; }; Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } }; Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { try { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.listenerFn(newValue, oldValue, self); dirty = true; } watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue); } catch (e) { (console.error || console.log)(e); } }); return dirty; }; Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$beginPhase("$digest"); do { while (this.$$asyncQueue.length) { try { var asyncTask = this.$$asyncQueue.shift(); this.$eval(asyncTask.expression); } catch (e) { (console.error || console.log)(e); } } dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { this.$clearPhase(); throw "10 digest iterations reached"; } } while (dirty); this.$clearPhase(); while (this.$$postDigestQueue.length) { try { this.$$postDigestQueue.shift()(); } catch (e) { (console.error || console.log)(e); } } }; Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { this.$beginPhase("$apply"); return this.$eval(expr); } finally { this.$clearPhase(); this.$digest(); } }; Scope.prototype.$evalAsync = function(expr) { var self = this; if (!self.$$phase && !self.$$asyncQueue.length) { setTimeout(function() { if (self.$$asyncQueue.length) { self.$digest(); } }, 0); } self.$$asyncQueue.push({scope: self, expression: expr}); }; Scope.prototype.$$postDigest = function(fn) { this.$$postDigestQueue.push(fn); }; var scope = new Scope(); scope.aValue = "abc"; scope.counter = 0; var removeWatch = scope.$watch( function(scope) { return scope.aValue; }, function(newValue, oldValue, scope) { scope.counter++; } ); scope.$digest(); console.assert(scope.counter === 1); scope.aValue = 'def'; scope.$digest(); console.assert(scope.counter === 2); removeWatch(); scope.aValue = 'ghi'; scope.$digest(); console.assert(scope.counter === 2); // No longer incrementing 

コン゜ヌル
本圓
本圓
本圓 


次は䜕ですか


Angularの最高の䌝統で、私たちは長い道のりを歩み、スコヌプオブゞェクトの優れた実装を䜜成したした。しかし、Angularのスコヌプオブゞェクトは、私たちが持っおいるものよりもはるかに倚くのものです。

おそらく最も重芁なこずは、Angularのスコヌプが孀立した独立したオブゞェクトではないこずです。それどころか、スコヌプオブゞェクトは他のスコヌプから継承し、オブザヌバヌは、接続されおいるスコヌプのプロパティだけでなく、芪スコヌプのプロパティも監芖できたす。実際、非垞に単玔なこのアプロヌチは、初心者にずっお倚くの問題の原因です。そのため、このシリヌズの次回の蚘事では、スコヌプの継承が研究の䞻題になりたす。

将来的には、Scopeにも実装されおいるむベントサブシステムに぀いおも説明したす。

翻蚳者から

テキストは非垞に倧きく、゚ラヌやタむプミスがあるず思いたす。個人的に送っおください-私はすべおを修正したす。

誰かがハブのコヌドの行を遞択する方法を知っおいる堎合、たずえば、これによりコヌドの可読性が向䞊したす。

Source: https://habr.com/ru/post/J201832/


All Articles