Оставаясь на сайте, вы принимаете Соглашение об использовании сайта, в том числе даете согласие на обработку файлов cookie и пользовательских данных.

11 советов по улучшению производительности AngularJS

1946
0
1946
0

Я новичок в Angular (хоть и не новичок в веб-разработке), поэтому воспринимайте все, что я говорю, с долей скептицизма. Тем не менее, я просмотрел множество обсуждений и прочел множество статей, связанных с производительностью Angular, и здесь привожу резюме накопленных знаний.


Оригинальная статья опубликована в блоге Алекса Краса в сентябре 2015 года. Все ссылки на другие источники — на английском языке.

1. Применяйте как можно меньше watcher-ов

Обычно, если Angular-приложение работает медленно, это означает, что в нем либо слишком много watcher-ов, либо эти watcher-ы работают больше, чем должны.

Angular использует «черновые» (прим.переводчика: в оригинале dirty) проверки, чтобы отслеживать все изменения в приложении. Это означает, что для проверки необходимости обновления данных будут задействованы все доступные watcher-ы (с помощью digest-цикла). Если же один из watcher-ов зависит от другого, Angular будет вынужден перезапустить digest-цикл снова, чтобы убедиться, что все изменения внесены, и это будет продолжаться до тех пор, пока все watcher-ы не обновятся.

Несмотря на то, что JavaScript в современных браузерах выполняется действительно быстро, в Angular совсем просто добавить столько watcher-ов, что скорость работы приложения заметно снизится.

При разработке или рефакторинге Angular-приложения важно иметь в виду:

  1. Watcher-ы создаются в следующих случаях:;
    • $scope.$watch;
    • при связывании с помощью {{ }};
    • в большинстве директив, таких как ng-show и т. д.;
    • при создании переменных в scope: {bar: ‘=’};
    • при использовании фильтров {{ value | myFilter}};
    • при использовании директивы ng-repeat.
  2. Watcher-ы (digest-цикл) выполняются в следующих случаях:
    • пользовательские действия (ng-click и т. д.) Большинство построений в директивах вызовут $scope.apply, по окончании которого запустится digest-цикл;
    • ng-change;
    • ng-model;
    • $http события (такие как AJAX-вызовы);
    • $q разрешения обещаний;
    • $timeout;
    • $interval;
    • ручной вызов $scope.apply и $scope.digest.

Полную презентацию по Angular можно посмотреть здесь.

2. Избегайте директивы ng-repeat. Если же их приходится применять, то используйте «бесконечный скроллинг» или постраничную навигацию

Этот совет дал нашему приложению самую ощутимую выгоду. Не буду вдаваться в детали, но вот статья, которая оказалась крайне полезной.

В дополнение к «бесконечному скроллингу» используйте по возможности track by (прим. переводчика: track by используется для сопоставления элемента массива с его DOM-элементом и по умолчанию используется track by $id(item), который генерирует $$hashKey для идентификации объекта).

Вот хороший пример использования уникального идентификатора при выполнении ng-repeat в совокупности с track by:

 <li ng-repeat="Task in Tasks track by Task.Id> </li>

3. По возможности используйте одноразовую привязку

В Angular 1.3 добавили нотацию «::», которая позволяет использовать одноразовую привязку данных. В итоге Angular ожидает значение, которое будет использоваться для визуализации DOM-элемента после первого выполнения digest-цикла. Затем Angular удалит wacther для этой привязки (см. здесь).

Если вы используете более старую версию Angular чем 1.3, то сможете достичь аналогичных результатов с помощью этой библиотеки.

4. Используйте $watchCollection вместо $watch (с 3-им параметром)

$watch только с 2 параметрами работает очень быстро. Однако, Angular поддерживает данный метод и с 3-м параметром. Выглядит это следующим образом: $watch(‘value’, function(){}, true). Третий параметр указывает Angular произвести глубокую проверку, то есть проверку каждого свойства объекта, и это может быть очень затратно.

Для решения данной проблемы создатели Angular добавили $watchCollection(‘value’, function(){}). Действия $watchCollection почти аналогичны действию $watch с 3-м параметром, за исключением того, что $watchCollection проверяет только первый уровень свойств объекта, значительно улучшая производительность.

Официальная документация

Полезная статья

5. Избегайте повторяющиеся фильтры и при первой возможности кэшируйте данные

Одноразовые привязки плохо подходят для работы с фильтрами. Мне кажется, есть способы заставить их работать в связке, но я думаю, что лучше и более интуитивно понятно будет просто присвоить нужное значение переменной (или установить несколько свойств объекта, если вы работаете с несколькими переменными).

Например, вместо:

{{‘DESCRIPTION’| translate}}

Можно использовать:

  • в JavaScript — $scope.description: $translate.instant(‘DESCRIPTION’)
  • в HTML — {{::description}}

Или вместо:

{{step.time_modified | timeFormatFilter}}
  • в JavaScript — var timeFormatFilter = $filter('timeFormatFilter');
    step.time_modified = timeFormatFilter(step.time_modified);
  • в HTML — {{::Path.time_modified}}.

6. Сокращайте количество вызовов ng-model

Если ng-model в приложении работает с большим количеством изменений, то вы можете объединить несколько вызовов функции в течение определенного времени в один (прим. переводчика: в оригинале для обозначения этого действия используется термин debounce).

Например, если у вас есть поле ввода данных для поиска, как в Google, то это можно сделать с помощью следующей опции:

ng-model-options=”{debounce: 250}”

Это обеспечит выполнение digest-цикла не чаще, чем один раз в 250 мс (подробнее можно почитать здесь).

7. Используйте ng-if вместо ng-show (но убедитесь, что в вашем случае использование ng-if действительно лучше)

ng-show визуализирует элемент и использует display:none, чтобы скрыть его, ng-if же фактически удаляет элемент из DOM и при необходимости пересоздает.

Для частого переключения состояний элементов «отобразить/скрыть» можно использовать и ng-show, но ng-if лучше подходит в 95% случаев.

8. Используйте console.time для оценки производительности функций

console.time — отличный API, и особенно полезным он оказывается при отладке проблем, связанных с производительностью Angular. Я вызывал эту функцию в коде не один раз, чтобы убедиться в том, что рефакторинг действительно улучшает производительность. Подробнее почитать можно здесь.

API выглядит следующим образом:

console.time("TimerName");
// Some code
console.timeEnd("TimerName");

И вот простой пример:

console.time("TimerName";);
setTimeout(function() {
    console.timeEnd("TimerName");
}, 100);
// In console $: TimerName: 100.324ms

Обратите внимание: если console.time для вас недостаточно точен, используйте performance.now(). Однако, в таком случае вычисления придется реализовывать самому (см. информативная презентация).

totalTime = 0; count = 0;
var someFunction = function() {
         var thisRunStartTime = performance.now();
         count++;
         // some code

         // some more code
         totalTime += performance.now() - thisRunStartTime;
};
console.log("Average time: " + totalTime/count);

9. Используйте нативный JavaScript или Lodash для медленных функций

прим. переводчика: Underscore.js — одна из самых известных и популярных JavaScript-библиотек. Но мало кто знает, что есть ее более удачный клон — Lodash.

При разработке приложения использовалась Lodash, поэтому ее применение в оптимизации не было связано ни с какими дополнительными расходами. Не будь же под рукой Lodash, мне бы, вероятнее всего, пришлось переписать все на нативном JavaScript.

В ходе тестов я обнаружил значительное увеличение производительности, связанное с переписыванием части базовой логики с помощью Lodash (вместо методов Angular, которые являются более обобщенными).

Джон Дэвид Далтон, евангелист Lodash и по совместительству один из создателей JSperf, знает о производительности все. Так что в вопросах производительности я всецело доверяю ему и его библиотеке.

10. Используйте Batarang для оценки производительности watcher-ов

Batarang — это отличный инструмент от команды Angular, очень полезный при отладке приложений. Он обладает большим количеством полезных функций, но самая подходящая в случае с оценкой производительности находится во вкладке Performance.

инструмент Angular для отладки приложений

Убедитесь, что у вас стабильная версия, работающая у большинства пользователей. А в этом видео вы найдете еще больше информации об этом инструменте.

11. Используйте Timeline и Profiler из Chrome Dev Tools для определения узких мест производительности

Себя я бы назвал достаточно опытным пользователем Chrome Dev Tools. Но мне нечасто приходится использовать вкладки Timeline и Profiler. В нынешнем же проекте эти две вкладки оказались особенно полезными.

Профессиональный совет: если вы используете API console.time (см. совет №8), то период времени будет выделен на шкале Timeline, так, что вы сможете изучать конкретные временные отрезки, вызывающие наибольшее беспокойство (см. подробнее здесь).

Временная шкала и магическая линия 60fps — самое важное. Когда я начинал работать над проектом, приложение полностью отрисовывалось за 15 и более секунд, практически не реагируя на действия пользователя.

Временная шкала

После оптимизации производительности приложение полностью отрисовывалось менее чем за 2 секунды (обратите внимание, что временная шкала отличается), что позволило пользователю свободно взаимодействовать с пользовательским интерфейсом после достаточно короткой паузы.

Временная шкала после оптимизации

По графикам четко видно, что приложение можно еще оптимизировать. Но даже текущим результатом я остался крайне доволен.

Чтобы узнать больше о Timeview, обратитесь к заметкам Пола Айриша.

Напоследок, я бы хотел упомянуть о вкладке Profiler в Chrome Dev Tools и о JavaScript CPU Profiler в частности. Данные в ней представлены в трех видах:

  1. График похож на Timeline, но обеспечивает легкий переход к исходному коду интересующей нас функции.
    график
  2. Heavy (Bottom up view). Это представление определяет тяжелые пользовательские функции и выводит стек вызовов, чтобы помочь точно определять происхождение функции. Обратите внимание, что $digest в списке идет до $apply, указывая на обратный порядок.
    heavy bottom
  3. Tree (Top down). Предоставляет список функций, из-за которых возникает большой расход и затем позволяет перейти к одной из них. Также обратите внимание на желтый треугольник с восклицательным знаком. Этот значок, как правило, указывает на потенциальные проблемы оптимизации.
    top down

Спасибо за внимание!

P.S. Совсем скоро состоится конференция по front-end разработке Большой Осенний Frontend Meetup. Данная статья выбрана специально к этому событию.