Введение
Сфера применения лежит в основной позиции в экосистеме NG. Основной слой двустороннего обязательства, заявленного NG, внешнему миру, фактически реализуется с помощью масштаба. В этой главе в основном анализируется механизм наблюдения, наследование и реализацию событий.
монитор
1. $ watch
1.1 Использовать
// $ watch: function (watchexp, слушатель, объективность)
var unwatch = $scope.$watch('aa', function () {}, isEqual);
Те, кто использовал Angular, часто используют приведенный выше код, обычно известный как «вручную» добавление прослушивания, а некоторые другие слушатели автоматически добавляют прослушивание через интерполяцию или директиву, но в принципе они одинаковы.
1.2 Анализ исходного кода
function (watchexp, слушатель, объект -экологичество) {var scope = this, // compile возможные строки в fn get = compiletOfn (watchexp, 'watch'), array = scope. $$ Watchers, Watcher = {fn: слушатель, последний: initwatchval, // Последнее значение записывается, чтобы решить. сравнение или сравнение значений}; LastDirtyWatch = null; if (! isfunction (слушатель)) {var listerfn = compiletotofn (слушатель || noop, 'слушатель'); watcher.fn = function (newval, oldval, scope) {listerfn (scope);}; } if (! Array) {array = scope. $$ watchers = []; } // Причина, по которой Unshift не выдвигается, заключается в том, что в $ Digest петля наблюдателей начинается с задней части/, чтобы позволить вновь добавленному наблюдателю быть выполненным в текущем цикле, он помещается в переднюю часть очереди. Unshift (Watcher); // return ulatchfn, отменить функцию возврата прослушивания dereGisterWatch () {arrayRemove (массив, наблюдатель); LastDirtyWatch = null; };}Из кода $ watch относительно просты. В основном это экономит наблюдателя в массиве $$ Watchers
2. $ Digest
При изменении значения объема объем не будет выполнять каждого наблюдателя слушателя сама по себе. Должно быть уведомление, и тот, кто посылает это уведомление, - это $ digest
2.1 Анализ исходного кода
Исходный код всего дайджеста $ составляет около 100 строк, а основная логика сосредоточена в грязном цикле проверки. Есть также некоторые незначительные коды после петли, такие как обработка Postdigestqueue, поэтому мы не будем подробно анализировать.
Цикл проверки грязного значения означает, что до тех пор, пока есть обновление значения наблюдателя, должен быть запущен раунд чеков, пока обновления значений не будут обновлены. Конечно, были сделаны некоторые оптимизации, чтобы уменьшить ненужные проверки.
Код:
// Введите цикл $ Digest и отметьте его, чтобы предотвратить повторный вход в Beartphase ('$ Digest'); LastDirtyWatch = null; // Запуск контрольного цикла грязных значений do {Dirty = false; current = target; // Asyncqueue Loop опускает TraverseScopesloop: do {if ((watchers = current. $$ watchers)) {length = watchers.length; while (длина--) {try {watch = watchers [длина]; if (Watch) {// Сделайте обновление, чтобы определить, обновлена ли значение, разложение следующим образом // value = watch.get (current), last = watch.last // value! == Последнее, если это правда, затем определите, необходимо ли сделать значение для суждения Watch.eq? equals (значение, последнее) // Если это не суждение равных значений, определить ситуацию nan, nan, nan! смотреть. // Записать, которые смотрят изменения в этом цикле LastDirtyWatch = Watch; // кэш последнего значения Watch.last = watch.eq? копия (значение, null): значение; // Выполнение прослушивания (NewValue, LastValue, Scope) // Если выполняется первое выполнение, то LastValue также устанавливается на NewValue Watch.fn (значение, ((последнее === initWatchVal)? Значение: последнее), current); // ... watchlog не опускает if (watch.get. $$ ulatch) stablewatchescandidates.push ({watch: watch, array: watchers}); } // Это оптимизация, чтобы уменьшить наблюдатель // Если последнее обновленное часы в предыдущем цикле не изменилось, то есть в этом раунде нет новых обновленных часов //, то это означает, что все часы были стабильными и не будут обновлены, и цикл заканчивается здесь. Остальные часы не нужно проверять, если (смотреть === LastDirtyWatch) {Dirty = false; Break Traversescopesloop; }}} catch (e) {clearphase (); $ ExceptionHandler (e); }}} // Этот раздел немного запутался, который фактически реализует глубину-первую обход // a-> [b-> d, c-> e] // Заказ выполнения A, B, D, C, E // Получить первого ребенка каждый раз. Если нет брата, если нет брата, то отступление к предыдущему слою и определите, есть ли братья в слое. Если нет, продолжайте отступать, пока он не отступит к стартовой области. В это время, следующее == null, поэтому цикл областей применения будет выведена, если (! (Next = (current. $$ childhead || (current! == target && current. $$ nextsibling)))) {while (current! == target &&! (Next = current. $$ nextsibling))) {current = current. $ Parent; }}} while (((current = Next)); // Break TraverseScopesloop идет непосредственно здесь // определить, находится ли он все еще в цикле грязного значения и превышает максимальное количество проверок ttl по умолчанию 10 if ((грязный || asyncqueue.length) &&! (ttl--)) {clearphase (); Бросьте $ ourcopeminerr ('infdig', '{0} $ digest () итерации достигнуты. }} while (грязный || asyncqueue.length); // конец петли // отмечать выход Digest Loop ClearPhase ();В приведенном выше коде есть 3-слойные петли
Первый слой судьи грязные, если есть грязная стоимость, а затем продолжайте цикть
делать {
// ...
} пока (грязный)
Второй слой определяет, был ли сфера применения. Код был переведен. Хотя это все еще распространяется, это можно понять.
делать {
// ....
if (current. $$ hildhead) {
Next = Current. $$ childhead;
} else if (current! == target && current. $$ nextsibling) {
Next = Current. $$ nextsibling;
}
while (! next && current! == target &&! (next = current. $$ nextsibling)) {
current = current. $ parent;
}
} while (current = Next);
Третий слой наблюдателей за областями цикла
длина = watchers.length;
while (длина-) {
пытаться {
Смотреть = наблюдатели [длина];
// ... опущен
} catch (e) {
clearPhase ();
$ ExceptionHandler (e);
}
}
3. $ Evalasync
3.1 Анализ исходного кода
$ evalasync используется для задержки выполнения, исходный код заключается в следующем:
function (expr) {if (! $ rootscope. $$ phase &&! $ rootscope. $$ asyncqueue.length) {$ browser.defer (function () {if ($ rootscope. $$ asyncqueue.length) {$ rootscope. $ digest ();}); } this. $$ asyncqueue.push ({scope: this, выражение: expr});}Оценив, уже работает грязный чек, или кто -то запустил $ evalasync
if (! $ rootscope. $$ Phase &&! $ ROOTSCOPE. $$ Asyncque
Если вы не используете DEFER, то
function (exp) {queue.push ({scope: this, выражение: exp}); это. $ digest ();} scope. $ evalasync (fn1); scope. $ evalasync (fn2); // Результат // $ digest ()> fn1> $ digest ()> fn2 // Но фактический эффект должен быть достигнут: $ digest ()> fn22 //, но фактический эффект должен бытьСодержание Async было опущено в предыдущем разделе $ Digest, который расположен в первом уровне цикла
while (asyncqueue.length) {try {asynctask = asyncqueue.shift (); asynctask.scope. $ eval (asynctask.expression); } catch (e) {clearphase (); $ ExceptionHandler (e); } lastDirtyWatch = null;}Простые и легко понимать, всплыть Asynctask для выполнения.
Но здесь есть детали, почему она настроена так? Причина заключается в следующем. Если новая Asynctask добавлена, когда WatchX выполняется в определенном цикле, в настоящее время будет установлен Watchx. Выполнение этой задачи приведет к выполнению нового значения в последующих часах WatchX. Если нет следующего кода, то цикл выпрыгнет из следующего цикла до LastDirtyWatch (WatchX) и Dirty == False в это время.
LastDirtyWatch = null;
Здесь также есть деталь, зачем петлю на первом уровне? Поскольку применение отношений наследования имеет $$ Asyncqueue, все установлено на корне, поэтому его не нужно выполнять в слое применения следующего слоя.
2. Наследство
Область применения наследуется, например, $ ParotyCope и $ ChildScope Two Scopes. Когда $ childscope.fn вызывается, если в $ Childscope нет метода FN, то перейдите к $ PartyCope, чтобы найти метод.
Ищите слой по слою, пока не найдете необходимый атрибут. Эта функция реализована с использованием характеристик прототипа наследования Javascipt.
Исходный код:
function (изолят) {var childscope, child; if (изолят) {child = new Scope (); ребенок. $ root = это. $ root; // Асинхроя и постдигестауэли изолята также являются общедоступными корнями и другими независимыми детьми. $$ asyncqueue = это. $$ asyncqueue; ребенок. $$ postdigestqueue = this. $$ postdigestqueue; } else {if (! this. $$ childscopeclass) {this. $$ childscopeclass = function () {// здесь мы можем видеть, какие атрибуты уникальны для изоляции, такие как $$ Watchers, поэтому они будут контролироваться независимо. Это. $$$ Watchers = this. $$ nextsibling = this. $$ hildhead = this. $$ Childtail = null; это. $$ слушатели = {}; это. $$ ListenerCount = {}; это. $ id = nextuid (); это. $$ ChildScopeClass = null; }; это. $$ childscopeclass.prototype = this; } child = new This. $$ ChildScopeClass (); } // Установить различные отношения отца и брата, что очень грязно! ребенок ['this'] = ребенок; ребенок. $ parent = это; Ребенок. $$ PRESSIBLIBLE = это. $$ Childtain; if (this. $$ hildhead) {this. $$ Childtain. $$ nextsibling = ребенок; это. $$ Childtain = ребенок; } else {this. $$ childhead = this. $$ Childtail = child; } возвращать ребенка;}Код ясен, основные детали - какие атрибуты должны быть независимыми, а какие должны основываться на основаниях.
Самый важный код:
this.$$childScopeClass.prototype = this;
Наследование реализуется таким образом.
3. Механизм событий
3.1 $ на
function (имя, слушатель) {var namelisteners = this. $$ прослушиватели [name]; if (! andlisteners) {this. $$ слушатели [name] = nameListeners = []; } nameListeners.push (слушатель); var ток = это; do {if (! current. $$ ListenerCount [name]) {current. $$ allustercount [name] = 0; } current. $$ ListenerCount [name] ++; } while (((current = current. $ parent)); var self = это; return function () {andlisteners [indexof (nameListeners, slister)] = null; DecrementListenerCount (Self, 1, имя); };}Подобно $ wathc, он также хранится в массиве - по имени Listeners.
Существует еще одно отличие того, что область объема и все родители сохраняют статистику событий, что полезно при вещательных событиях и последующем анализе.
var current = this; do {if (! current. $$ listenercount [name]) {current. $$ listenercount [name] = 0; } current. $$ прослушивание3.2 $ Emit
$ Emit - это событие вверх. Исходный код:
function (name, args) {var empty = [], natedListeners, scope = this, stoppropagation = false, event = {name: name, targetscope: scope, stoppropagation: function () {stoppropagation = true;}, Preventdefault: function () {event.defaultPrevent = true; }, defaultPrevented: false}, allusterargs = concat ([event], аргументы, 1), i, длина; do {nameListeners = scope. $$ слушатели [имя] || пустой; event.currentscope = scope; for (i = 0, length = nameListeners.length; i <length; i ++) {// После прослушивания удаления, он не будет удален из массива, но установлен на нулевой, поэтому необходимо судить, если (! natueListeners [i]) {natedListeners.Splice (i, 1); я--; длина--; продолжать; } try {nameListeners [i] .apply (null, слушание); } catch (e) {$ ExceptionHandler (e); }} // Когда распространение остановлено, верните if (stoppropagation) {event.currentscope = null; возвращение события; } // Emit - это способ распространения вверх acpope = scope. $ parent; } while (Scope); event.currentscope = null; return Event;}3.3 $ трансляция
$ Broadcast распространяется внутрь, то есть распространяется на ребенка, исходный код:
function (name, args) {var target = this, current = target, next = target, event = {name: name, targetscope: target, preventdefault: function () {event.defaultprevented = true; }, defaultPrevented: false}, allusterargs = concat ([event], аргументы, 1), слушатели, i, длина; while ((current = next)) {event.currentscope = current; Слушатели = Current. $$ Слушатели [Имя] || []; for (i = 0, length = слушатели. я--; длина--; продолжать; } try {слушатели [i] .apply (null, allusterargs); } catch (e) {$ ExceptionHandler (e); }} // if (next = ((current. $$ listenercount [name] && current. $$ hidehead) || (current! == target && current. $$ nextsibling)))) {while (current! == target &&! (Next = current. $$ nextsibling))) {current = current. $ Parent; }}} event.currentscope = null; return Event;}Другая логика относительно проста, то есть код, который проходит глубинный, более запутан. На самом деле, это то же самое, что в Digest. Это должно судить, есть ли слушание на пути. Current. $$ ListenerCount [имя]. Из приведенного выше $ в коде мы видим, что, пока на пути и слушатель есть ребенок, заголовок пути также имеет номер. Напротив, если не указано, что у всех детей на пути нет событий слушания.
if (! (next = ((current. $$ listenercount [name] && current. $$ holdhead) || (current! == target && current. $$ nextsibling)))) {while (current! == target &&! (next = current. $$ nextsibling)) {current = current. $ parent; }}Путь распространения:
Root> [a> [a1, a2], b> [b1, b2> [c1, c2], b3]]]
Root> a> a1> a2> b> b1> b2> c1> c2> b3
4. $ Watchcollection
4.1 Используйте примеры
$ scope.names = ['igor', 'matias', 'misko', 'james']; $ scope.datacount = 4; $ scope. $ watchcollection ('names', function (newnames, oldnames) {$ scope.datacount = newNames.length;}); ожидайте ($ scope.datacount) .toequal (4); $ scope. $ digest (); ожидаете ($ scope.datacount) .toequal (4); $ scope.names.pop (); $ scope. $ digest (); weard ($ scope.datacount) .toequal (3);4.2 Анализ исходного кода
function (obj, слушатель) {$ watchcollectionInterceptor. $ stateful = true; var self = это; var newvalue; var oldvalue; var OryoldValue; varueveryoldvalue = (слушатель.length> 1); var измененна = 0; var mediceTector = $ parse (obj, $ watchcollectionInterceptor); var InternalArray = []; var InternalObject = {}; var initrun = true; var oldlength = 0; // Определите, изменять ли изменение на основе возвращенной измененной функции $ watchcollectionInterceptor (_value) {// ... return изменяется; } // Вызовите настоящего прослушивателя через этот метод в качестве прокси -функции $ watchCollectionAction () {} вернуть это. $ WATCH (BediceTector, $ WatchCollectionAction);}Основная вена является частью кода, перехваченного выше. Следующее в основном анализирует $ WatchCollectionInterceptor и $ WatchCollectionAction
4.3 $ WatchCollectionInterceptor
функция $ watchcollectionInterceptor (_value) {newValue = _value; var newlength, key, bootnan, newitem, olditem; if (isundefined (newvalue)) возврат; if (! isobject (newvalue)) {if (oldvalue! == newvalue) {oldvalue = newValue; Изменен ++; }} else if (isarraylike (newvalue)) {if (oldvalue! == internalarray) {oldvalue = internalarray; OldLength = OldValue.Length = 0; Изменен ++; } newLength = newValue.length; if (oldlength! == newlength) {изменено ++; OldValue.length = OldLength = newLength; } for (var i = 0; i <newlength; i ++) {olditem = oldValue [i]; newItem = newValue [i]; bothnan = (olditem! == olditem) && (newitem! == newitem); if (! bothnan && (olditem! == newitem)) {изменено ++; OldValue [i] = newItem; }}} else {if (oldValue! == InternalObject) {oldValue = intemporoBject = {}; OldLength = 0; Изменен ++; } newLength = 0; для (ключ в newvalue) {if (hasownproperty.call (newvalue, key)) {newlength ++; newItem = newValue [Key]; Olditem = OldValue [Key]; if (ключ в OldValue) {bothnan = (olditem! == olditem) && (newitem! == newitem); if (! bothnan && (olditem! == newitem)) {изменено ++; OldValue [Key] = newItem; }} else {OldLength ++; OldValue [Key] = newItem; Изменен ++; }}} if (oldlength> newlength) {изменено ++; для (ключ в OldValue) {if (! hasownproperty.call (newvalue, key)) {oldlength--; Удалить OldValue [Key]; }}}} returneeTected;}1). Вернуть непосредственно, когда значение не определен.
2). Когда значение является обычным базовым типом, напрямую определите, является ли оно равным.
3). Когда значение представляет собой массив классов (то есть атрибут длины существует, а значение [i] также называется массивом классов), OldValue инициализируется сначала без инициализации
if (OldValue! == InternalArray) {OldValue = internalArray; OldLength = OldValue.Length = 0; Изменено ++;}Затем сравните длину массива, если не равна, он записывается как изменение изменяемого ++
if (oldlength! == newlength) {изменено ++; OldValue.length = OldLength = newLength;}Сравните один за другим
for (var i = 0; i <newlength; i ++) {olditem = oldValue [i]; newItem = newValue [i]; bothnan = (olditem! == olditem) && (newitem! == newitem); if (! bothnan && (olditem! == newitem)) {изменено ++; OldValue [i] = newItem; }}4). Когда значение является объектом, процесс инициализации аналогичен вышеуказанному
if (OldValue! == InternalObject) {OldValue = internalObject = {}; OldLength = 0; Изменено ++;}Следующая обработка более умела. Если вы обнаружите, что добавлены новые поля со многими новыми, добавьте 1 в старую длину, так что старая длина только добавляет и не вычитается. Легко найти, есть ли новые поля в Ньювале. Наконец, удалите дополнительные поля в OldValue, то есть удаленные поля в NewValue, а затем все закончится.
newLength = 0; for (key in newValue) {if (hasownproperty.call (newvalue, key)) {newlength ++; newItem = newValue [Key]; Olditem = OldValue [Key]; if (ключ в OldValue) {bothnan = (olditem! == olditem) && (newitem! == newitem); if (! bothnan && (olditem! == newitem)) {изменено ++; OldValue [Key] = newItem; }} else {OldLength ++; OldValue [Key] = newItem; Изменен ++; }}} if (oldlength> newlength) {изменено ++; для (ключ в OldValue) {if (! hasownproperty.call (newvalue, key)) {oldlength--; Удалить OldValue [Key]; }}}4.4 $ WatchCollectionAction
функция $ watchCollectionAction () {if (initrun) {initrun = false; Слушатель (Newvalue, Newvalue, Self); } else {слушатель (newvalue, soluervalue, self); } // TrackveryOldValue = (Slister.length> 1) Проверьте, требует ли метод слушателя OldValue // копировать при необходимости, если (TrackveryOldValue) {if (! isObject (newValue)) {sontholordValue = newValue; } else if (isArraylike (newValue)) {soluronValue = new Array (newValue.length); for (var i = 0; i <newvalue.length; i ++) {soliderValue [i] = newValue [i]; }} else {shereOldValue = {}; for (var key in newvalue) {if (hasownproperty.call (newvalue, key)) {sontholordvalue [key] = newvalue [key]; }}}}}}Код относительно прост, он должен вызвать прослушивание. Когда первый вызов - OldValue == newValue. Для эффективности и памяти определяется, нужен ли слушатель параметр OldValue.
5. $ eval & $ применить
$ eval: function (expr, локалы) {return $ parse (expr) (this, локалы);}, $ применить: function (expr) {try {beginphase ('$ применить'); вернуть это. $ eval (expr); } catch (e) {$ ExceptionHandler (e); } наконец {clearphase (); try {$ rootscope. $ Digest (); } catch (e) {$ ExceptionHandler (e); бросить E; }}}$ применить, наконец, вызовы $ rootscope. $ dighest (), поэтому многие книги рекомендуют использовать $ digest () вместо звонков $ Apply (), что более эффективно.
Основная логика - все в $ parse, которая принадлежит функции синтаксиса и будет проанализирована отдельно в будущем.