Улучшить эту документациюЧто такое области видимости?
Область видимости — это объект, который ссылается на модель приложения. Это контекст выполнения для выражений. Области видимости организованы в иерархическую структуру, которая имитирует структуру DOM приложения. Области видимости могут отслеживать выражения и распространять события.
Характеристики области видимости
-
Области видимости предоставляют API ($watch) для наблюдения за изменениями модели.
-
Области видимости предоставляют API ($apply) для распространения любых изменений модели через систему в представление извне «сферы AngularJS» (контроллеры, сервисы, обработчики событий AngularJS).
-
Области видимости могут быть вложенными, чтобы ограничить доступ к свойствам компонентов приложения, предоставляя доступ к общим свойствам модели. Вложенные области видимости являются либо «дочерними областями видимости», либо «изолированными областями видимости». «Дочерняя область видимости» (прототипически) наследует свойства от родительской области видимости. «Изолированная область видимости» — нет. Подробнее см. изолированные области видимости.
-
Области видимости предоставляют контекст, в котором оцениваются выражения. Например,
{{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!');
});
Иерархии областей видимости
В каждом приложении AngularJS есть ровно одна корневая область видимости, но может быть любое количество дочерних областей видимости.
Приложение может иметь несколько областей видимости, потому что директивы могут создавать новые дочерние области видимости. При создании новых областей видимости они добавляются как дочерние к родительской области видимости. Это создает древовидную структуру, которая параллельна DOM, где они прикреплены.
Раздел Директивы, создающие области видимости содержит дополнительную информацию о том, какие директивы создают области видимости.
Когда AngularJS оценивает {{name}}, он сначала ищет в области видимости, связанной с данным элементом, свойство name. Если такого свойства нет, он ищет в родительской области видимости и так далее до тех пор, пока не достигнет корневой области видимости. В JavaScript это поведение известно как прототипное наследование, и дочерние области видимости прототипически наследуют от своих родителей.
Этот пример иллюстрирует области видимости в приложении и прототипное наследование свойств. За примером следует диаграмма, изображающая границы областей видимости.
Обратите внимание, что AngularJS автоматически добавляет класс ng-scope к элементам, к которым прикреплены области видимости. Определение <style> в этом примере выделяет красным новые места расположения областей видимости. Дочерние области видимости необходимы, потому что повторный цикл оценивает {{name}} выражение, но в зависимости от того, в какой области видимости выражение оценивается, результат будет другим. Аналогично, оценка {{department}} прототипически наследует от корневой области видимости, поскольку это единственное место, где определено свойство department.
Получение областей видимости из DOM.
Области видимости прикрепляются к DOM как свойство данных $scope, и их можно получить для отладки. (Вряд ли кому-то понадобится получать области видимости таким образом внутри приложения.) Место, где корневая область видимости прикреплена к DOM, определяется расположением директивы ng-app. Как правило, ng-app размещается на элементе <html>, но его можно разместить и на других элементах, если, например, только часть представления должна управляться AngularJS.
Чтобы проверить область видимости в отладчике:
-
Щелкните правой кнопкой мыши по элементу, который вас интересует в вашем браузере, и выберите «инспектировать элемент». Вы должны увидеть отладчик браузера с выделенным элементом, по которому вы щелкнули.
-
Отладчик позволяет получить доступ к текущему выбранному элементу в консоли как переменной
$0. -
Чтобы получить связанную область видимости в консоли, выполните:
angular.element($0).scope()
scope() доступна только в том случае, если $compileProvider.debugInfoEnabled() равно true (что является значением по умолчанию). Распространение событий области видимости
Области видимости могут распространять события аналогично событиям DOM. Событие может быть передано дочерним областям видимости или отправлено родительским областям видимости.
Жизненный цикл области видимости
Обычный поток браузера при получении события заключается в выполнении соответствующей обратной функции JavaScript. После завершения обратной функции браузер повторно отображает DOM и возвращается к ожиданию новых событий.
Когда браузер вызывает JavaScript, код выполняется вне контекста выполнения AngularJS, что означает, что AngularJS не осведомлен об изменениях модели. Для правильной обработки изменений модели выполнение должно войти в контекст выполнения AngularJS с использованием метода $apply. Только изменения модели, выполняемые внутри метода $apply , будут должным образом учтены AngularJS. Например, если директива прослушивает события DOM, такие как ng-click, она должна вычислить выражение внутри метода $apply.
После вычисления выражения метод $apply выполняет $digest. На фазе $digest область видимости проверяет все $watch выражения и сравнивает их со значениями предыдущего цикла. Эта проверка изменений выполняется асинхронно. Это означает, что такое присвоение, как $scope.username="angular" , не приведет к немедленному уведомлению $watch, вместо этого уведомление $watch откладывается до фазы $digest. Эта задержка желательна, поскольку она объединяет несколько обновлений модели в одно уведомление $watch, а также гарантирует, что во время уведомления $watch не выполняются другие $watch. Если $watch изменяет значение модели, это заставит выполнение дополнительного цикла $digest.
-
Создание
Корневая область видимости создается во время запуска приложения сервисом $injector. Во время связывания шаблона некоторые директивы создают новые дочерние области видимости.
-
Регистрация наблюдателей
Во время связывания шаблона директивы регистрируют наблюдатели в области видимости. Эти наблюдатели будут использоваться для распространения значений модели в DOM.
-
Изменение модели
Для надлежащего наблюдения за изменениями необходимо вносить их только в scope.$apply(). API AngularJS делают это неявно, поэтому дополнительный вызов
$applyне требуется при выполнении синхронной работы в контроллерах или асинхронной работы с сервисами $http, $timeout или $interval. -
Наблюдение за изменениями
В конце
$apply, AngularJS выполняет цикл $digest в корневой области видимости, который затем распространяется по всем дочерним областям видимости. Во время цикла$digestвсе$watchвыражения или функции проверяются на изменения модели, и если изменение обнаружено, вызывается обработчик$watch. -
Уничтожение области видимости
Когда дочерние области видимости больше не нужны, создатель дочерней области видимости несет ответственность за их уничтожение через API scope.$destroy(). Это остановит распространение вызовов
$digestв дочернюю область видимости и позволит мусорному сборщику освободить память, используемую моделями дочерней области видимости.
Области видимости и директивы
В фазе компиляции компилятор сопоставляет директивы с шаблоном DOM. Директивы обычно делятся на две категории:
-
Директивы наблюдения, такие как выражения с двойными фигурными скобками
{{expression}}, регистрируют обработчики событий с помощью метода $watch(). Этот тип директивы должен уведомляться при каждом изменении выражения, чтобы она могла обновить представление. -
Директивы-обработчики событий, такие как ng-click, регистрируют обработчик событий в DOM. Когда срабатывает обработчик DOM, директива выполняет связанное выражение и обновляет представление с помощью метода $apply().
При получении внешнего события (например, действия пользователя, таймера или XHR), связанное выражение должно быть применено к области видимости через метод $apply(), чтобы все обработчики событий были обновлены правильно.
Директивы, создающие области видимости
В большинстве случаев директивы и области видимости взаимодействуют, но не создают новые экземпляры области видимости. Однако некоторые директивы, такие как ng-controller и ng-repeat, создают новые дочерние области видимости и прикрепляют дочернюю область видимости к соответствующему элементу DOM.
Особый тип области видимости — isolate область видимости, которая не наследует прототипически от родительской области видимости. Этот тип области видимости полезен для компонентных директив, которые должны быть изолированы от родительской области видимости. См. руководство по директивам для получения дополнительной информации об изолированных областях видимости в пользовательских директивах.
Обратите также внимание, что компонентные директивы, созданные с помощью вспомогательной функции .component(), всегда создают изолированную область видимости.
Контроллеры и области видимости
Области видимости и контроллеры взаимодействуют в следующих ситуациях:
-
Контроллеры используют области видимости для экспонирования методов контроллера в шаблонах (см. ng-controller).
-
Контроллеры определяют методы (поведение), которые могут изменять модель (свойства в области видимости).
-
Контроллеры могут регистрировать наблюдения за моделью. Эти наблюдения выполняются сразу после выполнения поведения контроллера.
См. ng-controller для получения дополнительной информации.
Соображения производительности наблюдения области видимости
Проверка области видимости на изменения свойств — распространённая операция в AngularJS, поэтому функция проверки изменений должна быть эффективной. Следует позаботиться о том, чтобы функция проверки изменений не выполняла доступ к DOM, так как доступ к DOM в разы медленнее, чем обращение к свойствам объекта JavaScript.
Глубины наблюдения области видимости
Проверка изменений может выполняться с помощью трёх стратегий: по ссылке, по содержимому коллекции и по значению. Стратегии отличаются видами обнаруживаемых изменений и характеристиками производительности.
- Наблюдение по ссылке (scope.$watch
(watchExpression, listener)) обнаруживает изменение, когда всё значение, возвращаемое выражением наблюдения, переходит к новому значению. Если значение является массивом или объектом, изменения внутри него не обнаруживаются. Это наиболее эффективная стратегия. - Наблюдение содержимого коллекции (scope.$watchCollection
(watchExpression, listener)) обнаруживает изменения, происходящие внутри массива или объекта: добавление, удаление или переупорядочивание элементов. Обнаружение является поверхностным — оно не достигает вложенных коллекций. Наблюдение содержимого коллекции дороже, чем наблюдение по ссылке, так как необходимо поддерживать копии содержимого коллекции. Однако стратегия пытается свести к минимуму количество необходимых копий. - Наблюдение по значению (scope.$watch
(watchExpression, listener, true)) обнаруживает любые изменения во вложенной структуре данных. Это самая мощная стратегия обнаружения изменений, но и самая дорогостоящая. При каждом цикле переваривания необходима полная проверка вложенной структуры данных, и требуется полная её копия в памяти.
Интеграция с циклом событий браузера
Диаграмма и пример ниже описывают, как AngularJS взаимодействует с циклом событий браузера.
- Цикл событий браузера ожидает прихода события. Событие — это взаимодействие пользователя, событие таймера или сетевое событие (ответ от сервера).
- Выполняется обратный вызов события. Это входит в контекст JavaScript. Обратный вызов может изменить структуру DOM.
- После выполнения обратного вызова браузер выходит из контекста JavaScript и повторно отрисовывает представление на основе изменений в DOM.
AngularJS изменяет обычный поток JavaScript, предоставляя свой собственный цикл обработки событий. Это разделяет JavaScript на классический и контекст выполнения AngularJS. Только операции, которые выполняются в контексте выполнения AngularJS, будут использовать преимущества привязки данных AngularJS, обработки исключений, наблюдения за свойствами и т.д... Также можно использовать $apply() для входа в контекст выполнения AngularJS из JavaScript. Имейте в виду, что во многих местах (контроллеры, сервисы) $apply уже вызван директивой, обрабатывающей событие. Явное обращение к $apply необходимо только при реализации пользовательских обратных вызовов событий или при работе с обратными вызовами сторонних библиотек.
- Вход в контекст выполнения AngularJS, вызвав область видимости
.$apply(stimulusFn), гдеstimulusFn— работа, которую вы хотите выполнить в контексте выполнения AngularJS. - AngularJS выполняет
stimulusFn(), которая обычно изменяет состояние приложения. - AngularJS входит в цикл $digest. Цикл состоит из двух меньших циклов, которые обрабатывают очередь $evalAsync и список $watch. Цикл $digest продолжает итерацию до тех пор, пока модель не стабилизируется, что означает, что очередь $evalAsync пуста, а список $watch не обнаруживает изменений.
- Очередь $evalAsync используется для планирования работ, которые должны выполняться вне текущей области видимости стека, но перед отрисовкой представления браузером. Это обычно делается с помощью
setTimeout(0), но подходsetTimeout(0)страдает медленностью и может привести к мерцанию представления, так как браузер отрисовывает представление после каждого события. - Список $watch — это набор выражений, которые могут измениться с момента последней итерации. Если изменение обнаружено, вызывается функция
$watch, которая обычно обновляет DOM с новым значением. - После завершения цикла AngularJS $digest, выполнение выходит из контекста AngularJS и контекста JavaScript. За этим следует повторная отрисовка DOM браузером для отражения любых изменений.
Вот объяснение того, как пример Hello world достигает эффекта привязки данных, когда пользователь вводит текст в текстовое поле.
- Во время фазы компиляции:
- Во время фазы выполнения:
- Нажатие '
X' клавиши заставляет браузер генерировать событиеkeydownна элементе управления вводом. - Директива input обрабатывает изменение значения поля ввода и вызывает $apply
("name = 'X';")для обновления модели приложения внутри контекста выполнения AngularJS. - AngularJS применяет изменения
name = 'X';к модели. - Начинается цикл $digest.
- Список $watch обнаруживает изменение свойства
nameи уведомляет интерполяцию, которая в свою очередь обновляет DOM. - AngularJS выходит из контекста выполнения, что, в свою очередь, выходит из события
keydownи с ним контекста выполнения JavaScript. - Браузер повторно отрисовывает представление с обновленным текстом.
- Нажатие '
© 2010–2018 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.7.8/docs/guide/scope