Как работает привязка данных в AngularJS?

Как работает привязка данных в AngularJS фреймворк?

Я не нашел технических деталей на их сайте. Более или менее понятно, как это работает, когда данные распространяются от вида к модели. Но как AngularJS отслеживает изменения свойств модели без сеттеров и геттеров?

Я обнаружил, что есть наблюдатели JavaScript, которые могут выполнять эту работу. Но они не поддерживаются в Internet Explorer 6 и Internet Explorer 7. Так как же AngularJS узнает, что я изменил, например, следующее и отразил это изменение в виде?

myobject.myproperty="new value";

14 ответов

Решение

AngularJS запоминает значение и сравнивает его с предыдущим значением. Это основная грязная проверка. Если есть изменение в значении, то это вызывает событие изменения.

$apply() метод, который вы называете, когда вы переходите из мира не-AngularJS в мир AngularJS, вызывает $digest(), Дайджест - это просто старая грязная проверка. Он работает во всех браузерах и полностью предсказуем.

В отличие от грязной проверки (AngularJS) и слушателей изменений ( KnockoutJS и Backbone.js): хотя грязная проверка может показаться простой и даже неэффективной (я расскажу об этом позже), оказывается, что она семантически верна все время, в то время как слушатели изменений имеют много странных угловых случаев и нуждаются в таких вещах, как отслеживание зависимостей, чтобы сделать его более семантически правильным. Отслеживание зависимостей KnockoutJS - это умная функция для проблемы, которой нет у AngularJS.

Проблемы со сменой слушателей:

  • Синтаксис ужасен, так как браузеры не поддерживают его изначально. Да, есть прокси, но они не являются семантически правильными во всех случаях, и, конечно, в старых браузерах нет прокси. Суть в том, что грязная проверка позволяет вам делать POJO, тогда как KnockoutJS и Backbone.js вынуждают вас наследовать от их классов и получать доступ к вашим данным через методы доступа.
  • Изменить коалесценцию. Предположим, у вас есть массив предметов. Скажем, вы хотите добавить элементы в массив, поскольку вы добавляете циклы, каждый раз, когда вы добавляете, вы запускаете события при изменении, то есть рендеринг пользовательского интерфейса. Это очень плохо для производительности. То, что вы хотите, это обновить пользовательский интерфейс только один раз, в конце. События изменения слишком детализированы.
  • Слушатели смены немедленно запускаются на установщике, что является проблемой, поскольку слушатель смены может дополнительно изменять данные, что вызывает больше событий изменения. Это плохо, поскольку в вашем стеке может быть несколько событий изменений, происходящих одновременно. Предположим, у вас есть два массива, которые по какой-либо причине необходимо синхронизировать. Вы можете добавлять только одно или другое, но каждый раз, когда вы добавляете, вы запускаете событие изменения, которое теперь имеет противоречивый взгляд на мир. Эта проблема очень похожа на блокировку потоков, которую JavaScript избегает, поскольку каждый обратный вызов выполняется исключительно и до завершения. События изменения прерывают это, поскольку сеттеры могут иметь далеко идущие последствия, которые не предназначены и неочевидны, что создает проблему потока снова и снова. Оказывается, что вы хотите сделать, это задержать выполнение слушателя и гарантировать, что одновременно работает только один слушатель, следовательно, любой код может свободно изменять данные, и он знает, что никакой другой код не выполняется, пока он это делает.,

Как насчет производительности?

Так что может показаться, что мы медлительны, поскольку грязная проверка неэффективна. Здесь мы должны смотреть на реальные числа, а не просто на теоретические аргументы, но сначала давайте определим некоторые ограничения.

Люди это:

  • Медленно - все, что быстрее 50 мс, незаметно для человека и поэтому может рассматриваться как "мгновенное".

  • Ограниченный - Вы не можете действительно показать больше чем приблизительно 2000 частей информации человеку на одной странице. Что-то большее, чем это, действительно плохой пользовательский интерфейс, и люди все равно не смогут это обработать.

Таким образом, реальный вопрос заключается в следующем: сколько сравнений вы можете сделать в браузере за 50 мс? На этот вопрос сложно ответить, так как в игру вступают многие факторы, но вот контрольный пример: http://jsperf.com/angularjs-digest/6 который создает 10 000 наблюдателей. В современном браузере это занимает чуть менее 6 мс. В Internet Explorer 8 это занимает около 40 мс. Как видите, в наши дни это не проблема даже для медленных браузеров. Есть предостережение: сравнения должны быть простыми, чтобы соответствовать ограничению по времени... К сожалению, слишком медленно добавлять медленное сравнение в AngularJS, поэтому легко создавать медленные приложения, когда вы не знаете, что вам нужно. делают. Но мы надеемся получить ответ, предоставив инструментальный модуль, который покажет вам, какие медленные сравнения.

Оказывается, что в видеоиграх и графических процессорах используется метод грязной проверки, особенно потому, что он последовательный. Пока они превышают частоту обновления монитора (обычно 50–60 Гц или каждые 16,6–20 мс), любая производительность, превышающая это, является пустой тратой, поэтому вам лучше рисовать больше материала, чем увеличивать FPS.

Миско уже дал отличное описание того, как работают привязки данных, но я хотел бы добавить свой взгляд на проблему производительности с привязкой данных.

Как сказал Миско, около 2000 привязок - это то место, где вы начинаете видеть проблемы, но вы все равно не должны иметь более 2000 фрагментов информации на странице. Это может быть правдой, но не каждая привязка данных видна пользователю. Как только вы начнете создавать виджеты или сетки данных с двусторонним связыванием, вы можете легко выполнить 2000 привязок, не имея плохого UX.

Рассмотрим, например, поле со списком, в котором вы можете ввести текст для фильтрации доступных параметров. Этот вид контроля может иметь ~150 предметов и при этом быть очень удобным. Если у него есть какая-то дополнительная функция (например, определенный класс для текущей выбранной опции), вы начинаете получать 3-5 привязок для каждой опции. Поместите три из этих виджетов на страницу (например, один для выбора страны, другой для выбора города в указанной стране и третий для выбора отеля), и вы уже находитесь в диапазоне от 1000 до 2000 привязок.

Или рассмотрите сетку данных в корпоративном веб-приложении. 50 строк на страницу не лишены смысла, каждая из которых может иметь 10-20 столбцов. Если вы построите это с помощью ng-повторов и / или будете иметь информацию в некоторых ячейках, которая использует некоторые привязки, вы можете приблизиться к 2000 привязкам только с этой сеткой.

Я считаю, что это огромная проблема при работе с AngularJS, и единственное решение, которое мне удалось найти, - это создание виджетов без двустороннего связывания, вместо использования ngOnce, отмены регистрации наблюдателей и аналогичных приемов, или создание директивы, которые строят DOM с помощью jQuery и манипулирования DOM. Я чувствую, что это побеждает цель использования Angular в первую очередь.

Я хотел бы услышать предложения о других способах справиться с этим, но тогда, возможно, я должен написать свой собственный вопрос. Я хотел поместить это в комментарии, но это оказалось слишком длинным для этого...

TL; DR
Привязка данных может вызвать проблемы с производительностью на сложных страницах.

Грязной проверкой $scope объект

Angular поддерживает простой array наблюдателей в $scope объекты. Если вы проверяете любой $scope вы обнаружите, что он содержит array называется $$watchers,

Каждый наблюдатель является object который содержит среди прочего

  1. Выражение, за которым следит наблюдатель. Это может быть просто attribute имя или что-то более сложное.
  2. Последнее известное значение выражения. Это можно проверить по текущему вычисленному значению выражения. Если значения отличаются, наблюдатель запустит функцию и отметит $scope как грязный
  3. Функция, которая будет выполнена, если наблюдатель загрязнен.

Как определяются наблюдатели

Есть много разных способов определения наблюдателя в AngularJS.

  • Вы можете явно $watch attribute на $scope,

    $scope.$watch('person.username', validateUnique);
    
  • Вы можете разместить {{}} интерполяция в вашем шаблоне (наблюдатель будет создан для вас на текущем $scope).

    <p>username: {{person.username}}</p>
    
  • Вы можете спросить директиву, такую ​​как ng-model определить наблюдателя для вас.

    <input ng-model="person.username" />
    

$digest цикл проверяет всех наблюдателей по их последнему значению

Когда мы взаимодействуем с AngularJS через обычные каналы (ng-модель, ng-repeat и т. Д.), Директива запускает цикл дайджеста.

Цикл дайджеста - это поиск в глубину $scope и все его дети. Для каждого $scopeobjectперебираем его $$watchersarray и оцените все выражения. Если новое значение выражения отличается от последнего известного значения, вызывается функция наблюдателя. Эта функция может перекомпилировать часть DOM, пересчитать значение на $scope вызвать AJAXrequest все, что вам нужно сделать.

Каждая область видимости прослеживается, и каждое выражение наблюдения оценивается и проверяется по последнему значению.

Если наблюдатель срабатывает, $scope грязный

Если запущен наблюдатель, приложение знает, что что-то изменилось, и $scope помечен как грязный

Функции наблюдателя могут изменять другие атрибуты на $scope или на родителя $scope, Если один $watcher функция сработала, мы не можем гарантировать, что наши другие $scope Они все еще чистые, поэтому мы снова выполняем весь цикл дайджеста.

Это потому, что AngularJS имеет двустороннюю привязку, поэтому данные могут быть переданы обратно $scope дерево. Мы можем изменить значение на более высокое $scope это уже переварено. Возможно, мы изменим значение на $rootScope,

Если $digest грязно, мы выполняем весь $digest цикл снова

Мы постоянно перебираем $digest цикл, пока либо цикл дайджеста не станет чистым (все $watch выражения имеют то же значение, что и в предыдущем цикле), либо мы достигаем предела дайджеста. По умолчанию этот предел установлен на 10.

Если мы достигнем предела дайджеста, AngularJS выдаст ошибку в консоли:

10 $digest() iterations reached. Aborting!

Дайджест сложен для машины, но легок для разработчика

Как видите, каждый раз, когда что-то меняется в приложении AngularJS, AngularJS будет проверять каждого наблюдателя в $scope иерархия, чтобы увидеть, как ответить. Для разработчика это огромный выигрыш в производительности, поскольку теперь вам практически не нужно писать код проводки, AngularJS просто заметит, изменилось ли значение, и сделает остальное приложение совместимым с этим изменением.

С точки зрения машины, хотя это крайне неэффективно и замедлит работу нашего приложения, если мы создадим слишком много наблюдателей. Миско привел цифру в 4000 зрителей, прежде чем ваше приложение будет работать медленнее в старых браузерах.

Этот предел легко достичь, если вы ng-repeat над большим JSONarray например. Вы можете избежать этого, используя такие функции, как однократное связывание для компиляции шаблона без создания наблюдателей.

Как избежать создания слишком большого количества наблюдателей

Каждый раз, когда ваш пользователь взаимодействует с вашим приложением, каждый наблюдатель в вашем приложении будет оцениваться как минимум один раз. Большая часть оптимизации приложения AngularJS - это уменьшение количества наблюдателей в вашем $scope дерево. Один из простых способов сделать это - одноразовая привязка.

Если у вас есть данные, которые редко изменяются, вы можете связать их только один раз, используя синтаксис::, например:

<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>

Это мое основное понимание. Это может быть неправильно!

  1. Элементы отслеживаются путем передачи функции (возвращающей объект, который нужно отслеживать) $watch метод.
  2. Изменения в наблюдаемых элементах должны быть внесены в блок кода, заключенный в $apply метод.
  3. В конце $apply $digest вызывается метод, который проходит через все часы и проверяет, изменились ли они с прошлого раза $digest побежал.
  4. Если какие-либо изменения найдены, то дайджест вызывается снова, пока все изменения не стабилизируются.

В обычной разработке синтаксис привязки данных в HTML говорит компилятору AngularJS создать часы для вас, а методы контроллера запускаются внутри $apply уже. Так что для разработчика приложений все прозрачно.

Я задумался об этом сам некоторое время. Без сеттеров как AngularJS Уведомление об изменениях в $scope объект? Это опрос их?

Что он на самом деле делает, так это: любое "нормальное" место, где вы модифицируете модель, уже вызывалось из кишок AngularJS, поэтому он автоматически вызывает $apply для вас после запуска вашего кода. Скажем, у вашего контроллера есть метод, который подключен к ng-click на каком-то элементе. Так как AngularJS связывает вызов этого метода для вас, у него есть шанс сделать $apply в соответствующем месте. Аналогично, для выражений, которые появляются прямо в представлениях, они выполняются AngularJS так это делает $apply,

Когда в документации говорится о необходимости позвонить $apply вручную для кода за пределамиAngularJS, речь идет о коде, который при запуске не вытекает из AngularJS сам в стеке вызовов.

Объясняя с картинками:

Привязка данных требует отображения

Ссылка в области не является точной ссылкой в ​​шаблоне. Когда вы связываете данные двух объектов, вам нужен третий, который слушает первый и модифицирует другой.

Здесь, когда вы изменяете <input> Дотронешься до данных-ref3. И классический механизм связывания данных изменит data-ref4. Так как другой {{data}} выражения будут двигаться?

События приводят к $digest()

Угловой поддерживает oldValue а также newValue каждой привязки. И после каждого углового события, знаменитый $digest() цикл проверит список наблюдения, чтобы увидеть, если что-то изменилось. Эти угловые события ng-click, ng-change, $http завершено... $digest() будет цикл до тех пор, как любой oldValue отличается от newValue,

На предыдущем рисунке он заметит, что data-ref1 и data-ref2 изменились.

Выводы

Это немного похоже на яйцо и курицу. Вы никогда не знаете, кто начинает, но, надеюсь, это работает большую часть времени, как ожидалось.

Другой момент заключается в том, что вы можете легко понять влияние простого связывания на память и процессор. Надеюсь, что настольные компьютеры достаточно толстые, чтобы справиться с этим. Мобильные телефоны не так сильны.

Очевидно, что нет периодической проверки Scope есть ли какие-либо изменения в объектах, прикрепленных к нему. Наблюдаются не все объекты, прикрепленные к области видимости. Область действия прототипа поддерживает $$ watchers. Scope только проходит через это $$watchers когда $digest называется.

Angular добавляет наблюдателя в $$ watchers для каждого из этих

  1. {{expression}} - в ваших шаблонах (и везде, где есть выражение) или когда мы определяем ng-модель.
  2. $ scope. $ watch ('expression / function') - В вашем JavaScript мы можем просто прикрепить объект области видимости для angular.

Функция$ watch принимает три параметра:

  1. Первая - это функция-наблюдатель, которая просто возвращает объект, или мы можем просто добавить выражение.

  2. Вторая функция слушателя, которая будет вызываться при изменении объекта. Все вещи, такие как изменения DOM, будут реализованы в этой функции.

  3. Третий является необязательным параметром, который принимает логическое значение. Если это правда, Angular 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 (). Предположим, что вы изменили модель $ scope в функции-обработчике с помощью директивы ng-click. В этом случае AngularJS автоматически запускает цикл $ digest, вызывая $digest(). Помимо ng-click, есть несколько других встроенных директив / сервисов, которые позволяют вам менять модели (например, ng-model, $ timeout и т. Д.) и автоматически запускает цикл $ digest. Примерная реализация $ 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() вручную, что запускает цикл $ digest. Аналогично, если у вас есть директива, которая устанавливает прослушиватель событий 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 обрабатывает механизм привязки данных с помощью трех мощных функций: $ watch (), $ digest () и $ apply (). В большинстве случаев AngularJS будет вызывать $ scope. $ Watch () и $ scope. $ Digest(), но в некоторых случаях вам, возможно, придется вызывать эти функции вручную для обновления с новыми значениями.

$ watch (): -

Эта функция используется для наблюдения за изменениями в переменной $ scope. Он принимает три параметра: выражение, слушатель и объект равенства, где слушатель и объект равенства являются необязательными параметрами.

$ digest () -

Эта функция перебирает все часы в объекте $ scope и его дочерних объектах $ scope
(если есть) Когда $ digest () выполняет итерацию по наблюдениям, он проверяет, изменилось ли значение выражения. Если значение изменилось, AngularJS вызывает слушателя с новым значением и старым значением. Функция $ digest () вызывается всякий раз, когда AngularJS считает это необходимым. Например, после нажатия кнопки или после вызова AJAX. В некоторых случаях AngularJS не может вызвать функцию $ digest (). В этом случае вы должны назвать это сами.

$ apply () -

Angular автоматически обновляет только те изменения модели, которые находятся в контексте AngularJS. Когда вы изменяете любую модель вне контекста Angular (например, события DOM браузера, setTimeout, XHR или сторонние библиотеки), вам необходимо сообщить Angular об изменениях, вызвав $ apply () вручную. Когда вызов функции $ apply () завершается, AngularJS вызывает $ digest () внутри, поэтому все привязки данных обновляются.

Случилось так, что мне нужно было связать модель данных человека с формой, и я сделал прямое сопоставление данных с формой.

Например, если модель имела что-то вроде:

$scope.model.people.name

Контрольный ввод формы:

<input type="text" name="namePeople" model="model.people.name">

Таким образом, если вы измените значение контроллера объекта, оно будет автоматически отражено в представлении.

Пример, в котором я передал модель, обновляется из данных сервера, когда вы запрашиваете почтовый индекс и почтовый индекс, основанный на письменной загрузке списка колоний и городов, связанных с этим представлением, и по умолчанию задаете первое значение для пользователя. И это я работал очень хорошо, что происходит, это то, что angularJS Иногда обновление модели занимает несколько секунд, для этого вы можете поставить счетчик во время отображения данных.

  1. Односторонняя привязка данных - это подход, при котором значение берется из модели данных и вставляется в элемент HTML. Нет способа обновить модель из вида. Используется в классических шаблонных системах. Эти системы связывают данные только в одном направлении.

  2. Привязка данных в приложениях Angular - это автоматическая синхронизация данных между компонентами модели и вида.

Привязка данных позволяет вам рассматривать модель как единый источник истины в вашем приложении. Представление является проекцией модели во все времена. Если модель изменилась, представление отражает изменение и наоборот.

привязка данных:

Что такое привязка данных?

Всякий раз, когда пользователь изменяет данные в представлении, происходит обновление этого изменения в модели области действия, и наоборот.

Как это возможно?

Краткий ответ: с помощью цикла дайджеста.

Описание: Angular js устанавливает наблюдателя на модель объема, которая запускает функцию слушателя, если в модели есть изменение.

$scope.$watch('modelVar' , function(newValue,oldValue){

// Dom обновляет код новым значением

});

Так когда и как вызывается функция наблюдателя?

Функция наблюдателя вызывается как часть цикла дайджеста.

Цикл дайджеста вызывается автоматически, как часть угловых js, встроенных в директивы / сервисы, такие как ng-model, ng-bind, $timeout, ng-click и другие..., которые позволяют запустить цикл дайджеста.

Функция цикла дайджеста:

$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope 

т.е.$rootScope.$apply()

Примечание: $apply() равно $rootScope.$ Digest() это означает, что грязная проверка начинается прямо с корня или вершины или родительской области видимости для всех дочерних областей $ в угловом приложении js.

Вышеперечисленные функции работают в браузере IE для упомянутых версий, также просто убедившись, что ваше приложение является приложением angular js, что означает, что вы используете файл сценария фреймворка angularjs, указанный в теге script.

Спасибо.

Вот пример привязки данных с 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

AngularJs поддерживает двухстороннюю привязку данных.
Означает, что вы можете получить доступ к данным 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 это данные, которые показываются пользователю в представлении и с которыми взаимодействует пользователь.
Так что легко связать данные в AngularJs.

Angular.js создает наблюдатель для каждой модели, которую мы создаем. Всякий раз, когда модель изменяется, к модели добавляется класс "ng-dirty", поэтому наблюдатель будет наблюдать за всеми моделями, имеющими класс "ng-dirty", и обновлять их значения в контроллере и наоборот.

Другие вопросы по тегам