Улучшить эту документациюЧто такое области видимости?
scope — это объект, который ссылается на модель приложения. Это контекст выполнения для выражений. Области видимости организованы в иерархическую структуру, которая имитирует структуру 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(). (API Angular делают это неявно, поэтому дополнительный вызов
$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 для получения дополнительной информации.
Учёт производительности наблюдения за областью видимости
Операция проверки изменений свойства области видимости является распространенной в Angular, и поэтому функция проверки изменений должна быть эффективной. Следует позаботиться о том, чтобы функция проверки изменений не выполняла доступ к DOM, поскольку доступ к DOM в сотни раз медленнее, чем доступ к свойствам объекта JavaScript.
Глубина наблюдения за областью видимости

Проверка изменений может выполняться с помощью трех стратегий: по ссылке, по содержимому коллекции и по значению. Стратегии отличаются типами обнаруживаемых изменений и характеристиками производительности.
- Наблюдение по ссылке (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 достигает эффекта привязки данных, когда пользователь вводит текст в текстовое поле.
- Во время компиляции:
- директивы ng-model и input директивы устанавливают обработчик событий изменения на элементе управления
keydown. - интерполяция interpolation устанавливает $watch для получения уведомлений об изменениях
name.
- директивы ng-model и input директивы устанавливают обработчик событий изменения на элементе управления
- Во время выполнения:
- Нажатие клавиши '
X' заставляет браузер генерировать событиеkeydownна элементе управления input. - Директива input фиксирует изменение значения input и вызывает $apply
("name = 'X';")для обновления модели приложения в контексте выполнения Angular. - Angular применяет
name = 'X';к модели. - Начинается цикл $digest.
- Список $watch обнаруживает изменение свойства
nameи уведомляет интерполяцию interpolation, которая в свою очередь обновляет DOM. - Angular завершает контекст выполнения, что, в свою очередь, завершает событие
keydownи с ним контекст выполнения JavaScript. - Браузер повторно рендерит представление с обновлённым текстом.
- Нажатие клавиши '
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.2.32/docs/guide/scope