Улучшить эту документациюЧто такое области видимости?
Область видимости — это объект, который ссылается на модель приложения. Это контекст выполнения для выражений. Области видимости организованы в иерархическую структуру, которая имитирует структуру DOM приложения. Области видимости могут отслеживать выражения и распространять события.
Характеристики области видимости
-
Области видимости предоставляют API ($watch) для наблюдения за изменениями модели.
-
Области видимости предоставляют API ($apply) для распространения любых изменений модели через систему в представление извне «области Angular» (контроллеры, сервисы, обработчики событий Angular).
-
Области видимости могут быть вложенными, чтобы ограничить доступ к свойствам компонентов приложения, одновременно предоставляя доступ к общим свойствам модели. Вложенные области видимости являются либо «дочерними областями видимости», либо «изолированными областями видимости». «Дочерняя область видимости» (прототипически) наследует свойства от родительской области видимости. «Изолированная область видимости» — нет. Смотрите изолированные области видимости для получения дополнительной информации.
-
Области видимости предоставляют контекст, в котором оцениваются выражения. Например,
{{username}}выражение не имеет смысла, пока оно не оценивается в контексте конкретной области видимости, которая определяет свойствоusername.
Область видимости как модель данных
Область видимости — это связующее звено между контроллером приложения и представлением. Во время фазы связывания шаблона директивы настраивают $watch выражения в области видимости. $watch позволяет директивам получать уведомления об изменениях свойств, что позволяет директиве отображать обновленное значение в DOM.
И контроллеры, и директивы имеют ссылку на область видимости, но не друг на друга. Эта организация изолирует контроллер от директивы, а также от DOM. Это важный момент, так как он делает контроллеры независимыми от представления, что значительно улучшает тестирование приложений.
В приведенном выше примере обратите внимание, что MyController присваивает World свойству username области видимости. Область видимости затем уведомляет input об этом присваивании, что затем отображает входное поле с предварительно заполненным именем пользователя. Это демонстрирует, как контроллер может записывать данные в область видимости.
Аналогично, контроллер может присваивать поведение области видимости, как видно по методу sayHello, который вызывается при нажатии пользователем кнопки «приветствовать». Метод sayHello может читать свойство username и создавать свойство greeting. Это демонстрирует, что свойства в области видимости обновляются автоматически, когда они связаны с HTML-элементами ввода.
Логически отображение {{greeting}} включает:
-
получение области видимости, связанной с узлом DOM, где
{{greeting}}определено в шаблоне. В этом примере это та же область видимости, что и область видимости, которая была передана вMyController. (Мы рассмотрим иерархии областей видимости позже.) -
оценить
greetingвыражение в отношении полученной выше области видимости и присвоить результат тексту окружающего элемента DOM.
Можно рассматривать область видимости и ее свойства как данные, используемые для рендеринга представления. Область видимости — единственный источник истины для всего, что связано с представлением.
С точки зрения тестируемости, разделение контроллера и представления желательно, потому что это позволяет нам тестировать поведение, не отвлекаясь на детали рендеринга.
it('should say hello', function() {
var scopeMock = {};
var cntl = new MyController(scopeMock);
// Assert that username is pre-filled
expect(scopeMock.username).toEqual('World');
// Assert that we read new username and greet
scopeMock.username = 'angular';
scopeMock.sayHello();
expect(scopeMock.greeting).toEqual('Hello angular!');
});
Иерархии областей видимости
В каждом приложении Angular существует ровно одна корневая область видимости, но может быть несколько дочерних областей видимости.
В приложении может быть несколько областей видимости, потому что некоторые директивы создают новые дочерние области видимости (обратитесь к документации по директивам, чтобы узнать, какие директивы создают новые области видимости). При создании новых областей видимости они добавляются как дочерние области видимости их родительской области видимости. Это создает древовидную структуру, которая параллельна структуре DOM, к которому они прикреплены.
Когда Angular оценивает {{name}}, он сначала проверяет область видимости, связанную с данным элементом, на наличие свойства name. Если такого свойства не найдено, он ищет его в родительской области видимости и так далее, пока не достигнет корневой области видимости. В JavaScript это поведение известно как прототипное наследование, и дочерние области видимости прототипически наследуют от своих родителей.
Этот пример иллюстрирует области видимости в приложении и прототипное наследование свойств. За примером следует диаграмма, отображающая границы областей видимости.
Обратите внимание, что Angular автоматически добавляет класс ng-scope к элементам, к которым прикреплены области видимости. Определение <style> в этом примере выделяет новые местоположения областей видимости красным цветом. Дочерние области видимости необходимы, потому что повторитель оценивает {{name}} выражение, но в зависимости от того, какая область видимости оценивает выражение, оно дает разные результаты. Аналогично, оценка {{department}} прототипически наследует от корневой области видимости, так как это единственное место, где определено свойство department.
Получение областей видимости из DOM.
Области видимости прикрепляются к DOM как свойство данных $scope, и их можно получить в целях отладки. (Вряд ли кому-то потребуется получать области видимости таким образом внутри приложения.) Место, где корневая область видимости прикрепляется к DOM, определяется местоположением директивы ng-app. Обычно ng-app размещается на элементе <html>, но его также можно разместить на других элементах, если, например, только часть представления должна контролироваться Angular.
Чтобы просмотреть область видимости в отладчике:
-
Наведите указатель мыши на интересующий элемент в браузере и выберите «Проверить элемент». Вы увидите отладчик браузера с выделенным элементом, на который вы нажали.
-
Отладчик позволяет получить доступ к текущему выбранному элементу в консоли как переменной
$0. -
Чтобы получить связанную область видимости в консоли, выполните:
angular.element($0).scope()или просто введите $scope
Распространение событий области видимости
Области видимости могут распространять события аналогично событиям DOM. Событие может быть рассылано дочерним областям видимости или отправлено родительским областям видимости.
Жизненный цикл области видимости
Обычный процесс браузера, получающего событие, заключается в выполнении соответствующего обратного вызова JavaScript. После завершения обратного вызова браузер повторно рендерит DOM и возвращается к ожиданию новых событий.
Когда браузер вызывает JavaScript, код выполняется вне контекста выполнения Angular, что означает, что Angular не знает о изменениях модели. Для правильной обработки изменений модели выполнение должно войти в контекст выполнения Angular с помощью метода $apply. Только изменения модели, которые выполняются внутри метода $apply, будут должным образом учтены Angular. Например, если директива прослушивает события DOM, такие как ng-click, она должна оценивать выражение внутри метода $apply.
После оценки выражения, метод $apply выполняет $digest. На фазе $digest область видимости проверяет все $watch выражения и сравнивает их со значением предыдущего цикла. Эта проверка на изменения выполняется асинхронно. Это означает, что присвоение, например, $scope.username="angular" не будет немедленно вызывать уведомление $watch, а уведомление $watch будет отложено до фазы $digest. Эта задержка желательна, поскольку она объединяет несколько обновлений модели в одно уведомление $watch, а также гарантирует, что во время уведомления $watch не выполняются другие $watch. Если $watch изменяет значение модели, он заставит выполнить дополнительные циклы $digest.
-
Создание
Корневая область видимости создается во время загрузки приложения службой $injector. Во время связывания шаблона некоторые директивы создают новые дочерние области видимости.
-
Регистрация наблюдателя
Во время связывания шаблона директивы регистрируют наблюдения за областью видимости. Эти наблюдения будут использоваться для распространения значений модели в DOM.
-
Изменение модели
Чтобы изменения модели были должным образом приняты, делайте их только внутри scope.$apply(). Angular API делают это неявно, поэтому дополнительный вызов
$applyне нужен при выполнении синхронной работы в контроллерах или асинхронной работы с сервисами $http, $timeout или $interval. -
Наблюдение за изменениями
В конце
$apply, Angular выполняет цикл $digest в корневой области видимости, который затем распространяется по всем дочерним областям видимости. Во время цикла$digest, все$watchвыражения или функции проверяются на изменения модели, и если изменение обнаружено, вызывается слушатель$watch. -
Уничтожение области видимости
Когда дочерние области видимости больше не нужны, создатель дочерней области видимости отвечает за их уничтожение с помощью API scope.$destroy(). Это прекратит распространение вызовов
$digestв дочернюю область видимости и позволит мусорному сборщику восстановить память, используемую моделями дочерней области видимости.
Области видимости и директивы
Во время фазы компиляции компилятор сопоставляет директивы с шаблоном DOM. Директивы обычно делятся на две категории:
-
Наблюдение за директивами, такими как выражения с двойными фигурными скобками
{{expression}}, регистрация слушателей с помощью метода $watch(). Этот тип директивы должен уведомляться всякий раз, когда выражение изменяется, чтобы она могла обновить представление. -
Директивы-слушатели, такие как ng-click, регистрируют слушателя в DOM. Когда слушатель DOM срабатывает, директива выполняет связанное выражение и обновляет представление с помощью метода $apply().
Когда поступает внешнее событие (например, действие пользователя, таймер или XHR), связанное выражение должно быть применено к области видимости через метод $apply(), чтобы все слушатели были обновлены правильно.
Директивы, создающие области видимости
В большинстве случаев директивы и области видимости взаимодействуют, но не создают новые экземпляры области видимости. Однако некоторые директивы, такие как ng-controller и ng-repeat, создают новые дочерние области видимости и прикрепляют дочернюю область видимости к соответствующему элементу DOM. Вы можете получить доступ к области видимости для любого элемента DOM, используя вызов метода angular.element(aDomElement).scope(). Дополнительную информацию об изолированных областях видимости см. в руководстве по директивам.
Контроллеры и области видимости
Области видимости и контроллеры взаимодействуют друг с другом в следующих ситуациях:
-
Контроллеры используют области видимости для экспонирования методов контроллера в шаблонах (см. ng-controller).
-
Контроллеры определяют методы (поведение), которые могут изменять модель (свойства в области видимости).
-
Контроллеры могут регистрировать наблюдения за моделью. Эти наблюдения выполняются сразу после выполнения поведения контроллера.
Дополнительную информацию см. в ng-controller.
Производительность наблюдения $watch области видимости
Обычная операция в Angular — это проверка области видимости на изменения свойств, и поэтому функция проверки изменений должна быть эффективной. Следует позаботиться о том, чтобы функция проверки изменений не выполняла доступ к DOM, так как доступ к DOM в разы медленнее, чем доступ к свойствам объекта JavaScript.
Глубины наблюдения $watch области видимости
Проверка изменений может выполняться с использованием трех стратегий: по ссылке, по содержимому коллекции и по значению. Стратегии различаются по типам обнаруживаемых изменений и по характеристикам производительности.
- Наблюдение по ссылке (scope.$watch
(watchExpression, listener)) обнаруживает изменение, когда все значение, возвращаемое выражением наблюдения, переключается на новое значение. Если значение представляет собой массив или объект, изменения внутри него не обнаруживаются. Это наиболее эффективная стратегия. - Наблюдение содержимого коллекции (scope.$watchCollection
(watchExpression, listener)) обнаруживает изменения, происходящие внутри массива или объекта: добавление, удаление или переупорядочение элементов. Обнаружение происходит поверхностно — оно не достигает вложенных коллекций. Наблюдение за содержимым коллекции дороже, чем наблюдение по ссылке, потому что необходимо поддерживать копии содержимого коллекции. Однако стратегия пытается свести к минимуму количество требуемых копий. - Наблюдение по значению (scope.$watch
(watchExpression, listener, true)) обнаруживает любые изменения в произвольно вложенной структуре данных. Это самая мощная стратегия обнаружения изменений, но и самая дорогая. Для каждого цикла обработки необходимо выполнить полное обход вложенной структуры данных, и необходимо хранить полную копию.
Интеграция с циклом событий браузера
Диаграмма и пример ниже описывают, как Angular взаимодействует с циклом событий браузера.
- Цикл событий браузера ожидает поступления события. Событие — это взаимодействие пользователя, событие таймера или сетевое событие (ответ от сервера).
- Выполняется обратный вызов события. Это входит в контекст JavaScript. Обратный вызов может изменить структуру DOM.
- После выполнения обратного вызова браузер выходит из контекста JavaScript и повторно рендерит представление на основе изменений в DOM.
Angular изменяет стандартный поток JavaScript, предоставляя собственный цикл обработки событий. Это разделяет JavaScript на классический и контекст выполнения Angular. Только операции, выполняемые в контексте выполнения Angular, получат выгоду от привязки данных Angular, обработки исключений, наблюдения за свойствами и т.д... Вы также можете использовать $apply(), чтобы войти в контекст выполнения Angular из JavaScript. Имейте в виду, что в большинстве мест (контроллеры, сервисы) $apply уже был вызван директивой, обрабатывающей событие. Явный вызов $apply необходим только при реализации пользовательских обратных вызовов событий или при работе с обратными вызовами сторонних библиотек.
- Перейти в контекст выполнения Angular, вызвав scope
.$apply(stimulusFn), гдеstimulusFn— работа, которую вы хотите выполнить в контексте выполнения Angular. - Angular выполняет
stimulusFn(), которая обычно изменяет состояние приложения. - Angular входит в цикл $digest. Цикл состоит из двух меньших циклов, которые обрабатывают очередь $evalAsync и список $watch. Цикл $digest продолжает повторяться до тех пор, пока модель не стабилизируется, что означает, что очередь $evalAsync пуста, а список $watch не обнаруживает никаких изменений.
- Очередь $evalAsync используется для планирования работы, которая должна выполняться вне текущей рамки стека, но перед рендерингом представления браузера. Это обычно делается с помощью
setTimeout(0), но подходsetTimeout(0)страдает медленностью и может привести к мерцанию представления, поскольку браузер рендерит представление после каждого события. - Список $watch — это набор выражений, которые могут измениться с момента последней итерации. Если обнаруживается изменение, то вызывается функция
$watch, которая обычно обновляет DOM новым значением. - После завершения цикла Angular $digest выполнение выходит из контекста Angular и JavaScript. За этим следует повторное рендеринг браузером DOM для отражения любых изменений.
Вот объяснение того, как пример Hello world достигает эффекта привязки данных, когда пользователь вводит текст в текстовое поле.
- Во время фазы компиляции:
- Во время фазы выполнения:
- Нажатие клавиши '
X' заставляет браузер генерировать событиеkeydownна элементе управления ввода. - Директива input фиксирует изменение значения ввода и вызывает $apply
("name = 'X';")для обновления модели приложения внутри контекста выполнения Angular. - Angular применяет
name = 'X';к модели. - Начинается цикл $digest
- Список $watch обнаруживает изменение свойства
nameи уведомляет интерполяцию, которая в свою очередь обновляет DOM. - Angular выходит из контекста выполнения, что в свою очередь выходит из события
keydownи с ним — из контекста выполнения JavaScript. - Браузер повторно рендерит представление с обновленным текстом.
- Нажатие клавиши '
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.3.20/docs/guide/scope