Spec-Zone.ru › Angular.js 1.8

Улучшить эту документациюЧто такое области видимости?

Область видимости — это объект, который ссылается на модель приложения. Это контекст выполнения для выражений. Области видимости организованы в иерархическую структуру, которая имитирует структуру 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.

Чтобы просмотреть область видимости в отладчике:

  1. Щелкните правой кнопкой мыши по интересующему элементу в браузере и выберите «Просмотреть элемент». Вы увидите отладчик браузера с выделенным элементом, на который вы щелкнули.

  2. Отладчик позволяет получить доступ к текущему выбранному элементу в консоли в виде переменной $0.

  3. Чтобы получить связанную область видимости в консоли, выполните: angular.element($0).scope()

Функция scope() доступна только тогда, когда $compileProvider.debugInfoEnabled() истинно (по умолчанию).

Распространение событий области видимости

Области видимости могут распространять события аналогично событиям 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.

  1. Создание

    Корневая область видимости создается во время запуска приложения сервисом $injector. Во время связывания шаблонов некоторые директивы создают новые дочерние области видимости.

  2. Регистрация наблюдателей

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

  3. Изменение модели

    Для того, чтобы изменения были должным образом замечены, их следует выполнять только в рамках scope.$apply(). API AngularJS делают это неявно, поэтому дополнительный вызов $apply не нужен при выполнении синхронной работы в контроллерах или асинхронной работы с сервисами $http, $timeout или $interval.

  4. Наблюдение за изменениями

    В конце $apply, AngularJS выполняет цикл $digest в корневой области видимости, который затем распространяется по всем дочерним областям видимости. Во время цикла $digest, все $watch выражения или функции проверяются на изменения модели, и если изменение обнаружено, вызывается обработчик $watch.

  5. Уничтожение области видимости

    Когда дочерние области видимости больше не нужны, создатель дочерней области видимости отвечает за их уничтожение с помощью 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 взаимодействует с циклом событий браузера.

  1. Цикл событий браузера ждет события. Событие — это взаимодействие пользователя, событие таймера или сетевое событие (ответ от сервера).
  2. Обработчик события выполняется. Это заходит в контекст JavaScript. Обработчик может изменить структуру DOM.
  3. После выполнения обработчика браузер покидает контекст JavaScript и повторно рендерит представление на основе изменений DOM.

AngularJS изменяет обычный поток JavaScript, предоставляя собственный цикл обработки событий. Это разделяет JavaScript на классический и контекст выполнения AngularJS. Только операции, выполняемые в контексте выполнения AngularJS, будут использовать преимущества привязки данных AngularJS, обработки исключений, наблюдения за свойствами и т. д. Вы также можете использовать $apply(), чтобы войти в контекст выполнения AngularJS из JavaScript. Имейте в виду, что в большинстве мест (контроллеры, сервисы) $apply уже вызван директивой, обрабатывающей событие. Явное обращение к $apply необходимо только при реализации пользовательских обработчиков событий или при работе с обработчиками событий сторонних библиотек.

  1. Войдите в контекст выполнения AngularJS, вызвав scope.$apply(stimulusFn), где stimulusFn — это работа, которую вы хотите выполнить в контексте выполнения AngularJS.
  2. AngularJS выполняет stimulusFn(), который обычно изменяет состояние приложения.
  3. AngularJS запускает цикл $digest. Цикл состоит из двух меньших циклов, которые обрабатывают очередь $evalAsync и список $watch. Цикл $digest продолжает итерацию до тех пор, пока модель не стабилизируется, то есть очередь $evalAsync пуста, а список $watch не обнаруживает никаких изменений.
  4. Очередь $evalAsync используется для планирования работы, которая должна выполняться вне текущей области стека, но до рендеринга представления браузером. Это обычно выполняется с setTimeout(0), но подход setTimeout(0) страдает медленностью и может привести к мерцанию представления, поскольку браузер рендерит представление после каждого события.
  5. Список $watch — это набор выражений, которые могли измениться с момента последней итерации. Если изменение обнаружено, вызывается функция $watch, которая обычно обновляет DOM с новым значением.
  6. После завершения цикла AngularJS $digest выполнение покидает контекст AngularJS и JavaScript. За этим следует повторное рендеринг DOM браузером для отражения любых изменений.

Здесь объясняется, как пример Hello world достигает эффекта привязки данных, когда пользователь вводит текст в текстовое поле.

  1. Во время фазы компиляции:
    1. директива ng-model и input директива настраивают слушатель keydown на элементе управления <input>.
    2. интерполяция интерполяция настраивает $watch для уведомления о name изменениях.
  2. Во время фазы выполнения:
    1. Нажатие клавиши 'X' заставляет браузер генерировать событие keydown на элементе управления ввода.
    2. директива input фиксирует изменение значения ввода и вызывает $apply("name = 'X';") для обновления модели приложения внутри контекста выполнения AngularJS.
    3. AngularJS применяет name = 'X'; к модели.
    4. Начинается цикл $digest
    5. Список $watch обнаруживает изменение свойства name и уведомляет интерполяцию, которая, в свою очередь, обновляет DOM.
    6. AngularJS выходит из контекста выполнения, что, в свою очередь, выходит из события keydown и с ним контекста выполнения JavaScript.
    7. Браузер повторно рендерит представление с обновленным текстом.

© 2010–2020 Google, Inc.
Licensed under the Creative Commons Attribution License 3.0.
https://code.angularjs.org/1.8.2/docs/guide/scope

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API