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

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

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

myobject.myproperty="new value";
1981
Pashec 13 Мар 2012 в 14:16

13 ответов

Лучший ответ

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, который создает 10000 наблюдателей. В современном браузере это занимает чуть менее 6 мс. В Internet Explorer 8 это занимает около 40 мс. Как видите, в наши дни это не проблема даже для медленных браузеров. Есть предостережение: сравнения должны быть простыми, чтобы соответствовать ограничению по времени ... К сожалению, слишком медленно добавить медленное сравнение в AngularJS, поэтому легко создавать медленные приложения, когда вы не знаете, что вам нужно. делают. Но мы надеемся получить ответ, предоставив инструментальный модуль, который покажет вам, какие медленные сравнения.

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

2743
Peter Mortensen 20 Дек 2015 в 21:39

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

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

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

enter image description here

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

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

enter image description here

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

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

Выводы

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

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

32
Nicolas Zozol 16 Сен 2017 в 07:36

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

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

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

62
Raghav Dinesh 6 Май 2018 в 00:43

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

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

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

81
Merlin 6 Янв 2017 в 08:02

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

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

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

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

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

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

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

322
Noor A Shuvo 5 Ноя 2018 в 12:56

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

4
dtabuenc 31 Окт 2016 в 00:30

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

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

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

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

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

Описание . 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 

То есть {{ Х0 }}

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

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

Спасибо.

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

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

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

6
Shankar Gangadhar 17 Июн 2017 в 19:28

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 () внутри, поэтому все привязки данных обновляются.

15
Raju 6 Ноя 2017 в 12:57

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

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

$scope.model.people.name

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

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

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

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

7
Merlin 6 Янв 2017 в 08:03

Очевидно, что нет {периодической проверки Scope, есть ли какие-либо изменения в объектах, прикрепленных к нему. Наблюдаются не все объекты, прикрепленные к области видимости. Область действия прототипа поддерживает $$ наблюдателей . 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();
       }
};
22
Merlin 6 Янв 2017 в 08:04

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

5
AllJs 6 Апр 2016 в 18:15

AngularJs поддерживает двустороннюю привязку данных .
Означает, что у вас есть доступ к данным Вид -> Контроллер и Контроллер -> Просмотр .

Например,

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.

5
ojus kulkarni 13 Сен 2016 в 07:58