Улучшить эту документациюЧто такое области видимости?
Область видимости — это объект, который ссылается на модель приложения. Это контекст выполнения для выражений. Области видимости организованы в иерархическую структуру, которая имитирует структуру 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.
-
Создание
Главная область видимости (root scope) создается во время запуска приложения сервисом $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на<input>элементе управления. - интерполяция интерполяция настраивает $watch для уведомления об изменениях
name.
- директива ng-model и input директива настраивают слушателя
- Во время фазы выполнения:
- Нажатие клавиши '
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.4.14/docs/guide/scope