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

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

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

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

myobject.myproperty="new value";

14 ответов


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

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

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

проблемы с изменением слушатели:

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

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

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

люди:

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

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

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

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


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

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

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

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

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

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

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


путем грязной проверки $scope объект

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

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

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

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

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

  • вы можете явно $watch an attribute on $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 и все его дети. Для каждого $scope object, повторяем ее $$watchers array и оцените все выражения. Если новое значение выражения отличается от последнего известного значения, вызывается функция наблюдателя. Эта функция может перекомпилировать часть DOM, пересчитать значение на $scope, вызвать AJAX request, все, что нужно.

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

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

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

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

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

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

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

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

10 $digest() iterations reached. Aborting!

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

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

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

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

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

каждый раз, когда пользователь взаимодействует с вашим приложением, каждый наблюдатель в вашем приложении будут оценены по крайней мере один раз. Большая часть оптимизации приложения 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 изменения извещения


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

привязка данных требует сопоставления

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

enter image description here

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

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

enter image description here

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

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

выводы

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

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


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

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

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

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

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

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

  3. третий является необязательным параметром, который принимает логическое значение . Если его true, 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.$дайджест.)( Предположим, что изменить модель $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.$дайджест(), но в некоторых случаях может потребоваться вызвать эти функции вручную для обновления с новыми значениями.

$watch () :-

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

$digest () -

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

$apply () -

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


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

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

$scope.model.people.name

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

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

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

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


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

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

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

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

короткий ответ : С помощью цикла усвоения.

описание : Угловой 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 () это означает, что грязная проверка начинается прямо с корневой или верхней или родительской области вплоть до всех дочерних областей $в приложении angular js.

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

спасибо.


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

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

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


вот пример привязки данных с помощью 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 поддерживает двусторонняя привязка данных.
Означает, что вы можете получить доступ к данным Вид -> Контроллер & -12-->Посмотреть

Для Ex.

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>

здесь, Если вы хотите использовать input name в контроллере затем,

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng-model связывает наше представление и отображает его в выражении {{ }}.
ng-model - это данные, которые отображаются пользователю в представлении и с которыми взаимодействует пользователь.
Таким образом, легко связать данные в AngularJs.


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