Многие статьи об оптимизации производительности, в первую очередь пытаются «заглянуть под капот» Angular и перегружают читателя информацией о внутренней организации фреймворка. Знакомство с внутренними механизмами работы очень важно, но в данной статье я попытался собрать самые простые примеры, которые оказывают наибольшее влияние на производительность приложения и помогают максимально быстро решить типичные проблемы.
Измерять производительность приложения удобно расширением Batarang для браузера Chrome. Этот инструмент показывает время выполнения каждого выражения:
Измерять количество самих наблюдателей (watchers) удобно расширением Angular watchers.
Восприятие приложения во много зависит от времени выполнения цикла $digest. Misko Hevery в своем знаменитом ответе на stackoverflow говорит, что любые изменения быстрее 50 ms незаметны для человека и следовательно их можно рассматривать как «мгновенные». Соответственно, чтобы пользователи не чувствовали «притормаживаний», мы должны уложиться в 50–100 ms на каждом $digest.
В каждом цикле $digest вызываются все watcher-функции и проверяется вся scope-модель на наличие изменений. Условно говоря, время выполнения цикла $digest = количество наблюдателей * время их выполнения. Таким образом, чтобы оптимизировать работу $digest нужно или уменьшить количество наблюдателей или увеличить скорость их вычисления.
Проверка изменений $scope.myVar будет происходить минимум один-два раза при каждом цикле $digest. Поэтому, старайтесь избегать дорогостоящих вычислений под наблюдением.
Если вы скрываете блок с помощью ng-hide или ng-show, внутренние элементы не удаляются из DOM, а просто скрываются через CSS-стиль display: none. Поэтому все {{expression}} внутри этих элементов будут вычисляться при каждом проходе $digest.
Не наблюдайте за переменными в невидимых элементах, или используйте ng-switch, чтобы удалить скрытые элементы из самого DOM.
Каждый фильтр в AngularJS выполняется минимум один-два раза при каждом цикле $digest. Старайтесь не использовать тяжеловесные вычисления в логике фильтров.
При каждом обращении к $http (и получении ответа) вызывается цикл $digest. Сократите количество обращений к серверу, модифицировав передаваемые данные.
В данном примере, Angular будет наблюдать не только за {{est}}, но и за всем текстом внутри <p>. Таким образом, весь текст (а он может быть действительно большим!) будет хранится в памяти. Чтобы избежать подобных ситуаций, используйте привязку ng-bind:
Директива ng-repeat является одной из самых затратных с точки зрения производительности. Каких-либо простых способов ее оптимизации не существует. Поэтому, избегайте ng-repeat при работе с большим массивом данных. Урезайте набор данных до того, как они попадут в ng-repeat.
Начиная с версии 1.3 в AngularJS появилась такая интересная особенность как одноразовое связывание (one-time binding). Обычно значения передаются в DOM таким образом:
Чтобы использовать одноразовое связывание, нужно добавить :: перед значением:
При одноразовом связывании, $watch удалится после вывода первых данных, а значит мы выиграем в производительности. С другой стороны, любые обновления модели не повлияют на представление.
В конечно итоге, самым простым способом оптимизации Angular, является уменьшение количества наблюдателей. Механизм двухстороннего связывания насколько удобен, что очень часто мы используем его там, где это неоправданно. Всем известно о теоретическом пределе «2000 наблюдателей», но если бы мы задумались об удобстве пользователей, то даже близко не подобрались бы к этому пределу.
Действительно ли наши пользователи могут осмыслить одновременное обновление двух тысяч переменных? Может лучше модифицировать приложение таким образом, чтобы внимание пользователя было сосредоточено только на важных в данный момент элементах управления? Это не только улучшит пользовательский опыт, но и сократит количество циклов $digest, выполняемых в единицу времени.
Ускоряем angular.js
Speeding up AngularJS
Angular: Performance
Чем измерять?
Измерять производительность приложения удобно расширением Batarang для браузера Chrome. Этот инструмент показывает время выполнения каждого выражения:
Измерять количество самих наблюдателей (watchers) удобно расширением Angular watchers.
К чему стремиться?
Восприятие приложения во много зависит от времени выполнения цикла $digest. Misko Hevery в своем знаменитом ответе на stackoverflow говорит, что любые изменения быстрее 50 ms незаметны для человека и следовательно их можно рассматривать как «мгновенные». Соответственно, чтобы пользователи не чувствовали «притормаживаний», мы должны уложиться в 50–100 ms на каждом $digest.
Как этого добиться?
В каждом цикле $digest вызываются все watcher-функции и проверяется вся scope-модель на наличие изменений. Условно говоря, время выполнения цикла $digest = количество наблюдателей * время их выполнения. Таким образом, чтобы оптимизировать работу $digest нужно или уменьшить количество наблюдателей или увеличить скорость их вычисления.
$scope.$watch
$scope.myVar = function() {
return 2 * 2;
}
$scope.$watch(myVar, function() {
alert('myVar has changed!');
});
Проверка изменений $scope.myVar будет происходить минимум один-два раза при каждом цикле $digest. Поэтому, старайтесь избегать дорогостоящих вычислений под наблюдением.
ng-show и ng-switch
Если вы скрываете блок с помощью ng-hide или ng-show, внутренние элементы не удаляются из DOM, а просто скрываются через CSS-стиль display: none. Поэтому все {{expression}} внутри этих элементов будут вычисляться при каждом проходе $digest.
Не наблюдайте за переменными в невидимых элементах, или используйте ng-switch, чтобы удалить скрытые элементы из самого DOM.
{{myName|filter}}
Каждый фильтр в AngularJS выполняется минимум один-два раза при каждом цикле $digest. Старайтесь не использовать тяжеловесные вычисления в логике фильтров.
$http
При каждом обращении к $http (и получении ответа) вызывается цикл $digest. Сократите количество обращений к серверу, модифицировав передаваемые данные.
ng-bind
<p>Lorem ipsum dolor sit amet ... mollit anim id {{est}} laborum.</p>
В данном примере, Angular будет наблюдать не только за {{est}}, но и за всем текстом внутри <p>. Таким образом, весь текст (а он может быть действительно большим!) будет хранится в памяти. Чтобы избежать подобных ситуаций, используйте привязку ng-bind:
<p>Lorem ipsum dolor sit amet ... mollit anim id <span ng-bing="est"></span> laborum.</p>
ng-repeat
Директива ng-repeat является одной из самых затратных с точки зрения производительности. Каких-либо простых способов ее оптимизации не существует. Поэтому, избегайте ng-repeat при работе с большим массивом данных. Урезайте набор данных до того, как они попадут в ng-repeat.
{{::value}}
Начиная с версии 1.3 в AngularJS появилась такая интересная особенность как одноразовое связывание (one-time binding). Обычно значения передаются в DOM таким образом:
<h1>{{title}}</h1>
Чтобы использовать одноразовое связывание, нужно добавить :: перед значением:
<h1>{{::title}}</h1>
При одноразовом связывании, $watch удалится после вывода первых данных, а значит мы выиграем в производительности. С другой стороны, любые обновления модели не повлияют на представление.
UI
В конечно итоге, самым простым способом оптимизации Angular, является уменьшение количества наблюдателей. Механизм двухстороннего связывания насколько удобен, что очень часто мы используем его там, где это неоправданно. Всем известно о теоретическом пределе «2000 наблюдателей», но если бы мы задумались об удобстве пользователей, то даже близко не подобрались бы к этому пределу.
Действительно ли наши пользователи могут осмыслить одновременное обновление двух тысяч переменных? Может лучше модифицировать приложение таким образом, чтобы внимание пользователя было сосредоточено только на важных в данный момент элементах управления? Это не только улучшит пользовательский опыт, но и сократит количество циклов $digest, выполняемых в единицу времени.
Ссылки по теме
Ускоряем angular.js
Speeding up AngularJS
Angular: Performance