Javascript-форум (https://javascript.ru/forum/)
-   Angular.js (https://javascript.ru/forum/angular/)
-   -   Angular: Performance optimization / Оптимизация производительности (https://javascript.ru/forum/angular/42001-angular-performance-optimization-optimizaciya-proizvoditelnosti.html)

nerv_ 09.10.2013 01:02

Angular: Performance optimization / Оптимизация производительности
 
Всем привет

В этой теме хотелось бы обсудить вопрос оптимизации производительности в ангуляре: как правильно писать код; чего следовало бы избегать; рецепты оптимизации производительности и т.п.

---

Ниже выжимка из всей темы

СОВЕТЫ ПО ОПТИМИЗАЦИИ ПРОИЗВОДИТЕЛЬНОСТИ

расширенное объяснение по каждому пункту можно найти почитав тему
  1. Если возможно то не слушать объекты и массивы целиком
  2. Без крайней нужды не вешать WATCH на функции
  3. Не вызывать
    $scope.$apply();
    $scope.$digest();
    
    слишком часто (например, в обработчиках событий scroll или mousemove)
  4. Не выводить в
    <element ng-repeat="..."></element>
    
    тысячи элементов, а вывести первые элементы и сделать динамическую подгрузку
  5. Если элемент скрыт с помощью ng-show или ng-hide
    <element ng-show="false"></element>
    <element ng-hide="true"></element>
    
    и внутри него есть тяжелые вычисления, вероятно, имеет смыл использовать [ng-if|ng-switch] чтобы их избежать
  6. Использовать синтаксис
    <element ng-repeat="item in array track by $index"></element> или
    <element ng-repeat="item in array track by item.id"></element>
    
    вместо
    <element ng-repeat="item in array"></element>
    
    чтобы отслеживать изменения по индексу или id, а не по значению
  7. Обновлять текущий и вложенные скопы
    $scope.$digest();
    
    а не все приложение
    $scope.$apply();
    

DjDiablo 09.10.2013 04:14

Я уже касался частично.

По scope.
1) Если возможно то не слушать объекты и массивы целиком. (так как ангуляру нужно выполнить цикл по всем свойствам)
2) Без крайней нужды не вешать WATCH на функции. (они будут срабатывать при любом изменении scope)
3) Подозреваю что события могут быть быстрее чем watch в ряде ситуаций, но не проверял.


По шаблону.
Мне очень интересно как ведут себя директивы помещенный внутрь ngrepeat. Не получится ли так что каждая директива внутри цикла будет пересоздаваться когда ngrepeat срабатывает повторно. Я полагаю что так оно и есть, а это означает что нужно дважды подумать прежде чем пихать директивы внутрь ngrepeat. Впрочем с несколькими сотнями итераций проблем быть не должно, конечно только если вы не обновляете данные несколько раз в секунду :) А вот на мобиле ХЗ сколько потянет.

Ну и просто философия. :)
Никто нативные операции на дум не отменял, и jQuery пока жив :) Если директива тяжелая по производительности, то никто не обязывает вас использовать шаблоны и связывание. Вы можете просто переписать внутренности директивы на голом JS или jQuery и получить быструю и максимально оптимизированную директиву. Ну к примеру в шаблоне директивы есть ng-show, возможно вы сумеете заменить ее нативным .style.display который однозначно сработает быстрее.

nerv_ 09.10.2013 13:21

DjDiablo, спасибо.

Еще у меня вот такие мысли ng-if, ng-switсh etc. как способ оптимизации производительности. Что думаете по этому поводу?

Цитата:

Сообщение от DjDiablo
Не получится ли так что каждая директива внутри цикла будет пересоздаваться когда ngrepeat срабатывает повторно. Я полагаю что так оно и есть

думаю, так оно и есть (судя по поведению)

Цитата:

Сообщение от DjDiablo
Никто нативные операции на дум не отменял, и jQuery пока жив

тогда зачем ангуляр использовать? :)

DjDiablo 09.10.2013 13:52

Тест не честный чуточку :). Поместив test() в ngShow ты заставил срабатывать test при любом изменении, но это не означает что срабатывает сам ngShow. Test вызывается только для того чтобы оно вернуло true или false, и ngShow получив это значение мог правильно отработать. Если бы ты слушал свойство а не функцию test то ngShow срабатывал бы только при изменении свойства. NgShowController:Controller:test и NgIfController:Controller:test не должны были оказаться в консоли если бы выводились самим ngShow .

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

Тем не менее ng-if это прикольная тема. Допустим в табах можно попробывать выключать содержимое невидимых закладок или чо нибудь в этом духе.

nerv_ 02.11.2013 15:13

Цитата:

Сообщение от DjDiablo
Тест не честный чуточку

а у меня честных тестов не бывает :D

Цитата:

Сообщение от DjDiablo
Допустим в табах можно попробывать выключать содержимое невидимых закладок или чо нибудь в этом духе.

или, например, при пагинации на клиенте или любые другие "не нужные" в данный момент куски разметки вместе с кодом

Об очевидном. Еще лучше не делать так:
element.bind( 'mousemove scroll ...', function() {
    $scope.$apply(function() { /* code here */ );
});

не вызывать пересчет скопа каждый раз в событиях, которые срабатывают достаточно часто, а по возможности использовать прокси

Shitbox2 05.11.2013 12:32

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

De-Luxis 07.11.2013 16:27

Цитата:

Сообщение от Shitbox2 (Сообщение 279596)
Не выводить в ng-repeat тысячи элементов, а сделать динамическую подгрузку и выгрузку первых элементов (как чат вконтакте). Тогда и с оптимизацией директивы можно не париться)

Ограничиваю список через фильтр. Этакий пэджинатор.
app.filter('startFrom', function() {
		return function(input, start) {
			start = parseInt(start);
			return input.slice(start);
		}
	})


И пишу в html:
<button ng-repeat="(n, button) in selectorField.items | startFrom:(currentPage - 1) * limit | limitTo:limit" ng-mousedown="setSelector(n)" ng-bind-html-unsafe="button.title"></button>


Но сотнях не тестировал, но работает шустро. Что думаете?

Shitbox2 09.11.2013 20:53

Цитата:

Что думаете?
Надо потестить)

Чуть не забыл. Еще одна фишка для оптимизации.
тыц1 и тыц2.

Суть в том, что вешать события на $rootScope нужно внимательно, т. к. при удалении шаблона с контроллером корневая область видимости никуда не девается и обработчик остается.

nerv_, посмотри в своем загрузчике файлов, кажется у тебя там именно такая ситуация. Т.е. если я закрою страничку загрузки файлов, то все обработчики так и останутся на $rootScope

nerv_ 09.11.2013 23:57

Цитата:

Сообщение от Shitbox2
nerv_, посмотри в своем загрузчике файлов, кажется у тебя там именно такая ситуация. Т.е. если я закрою страничку загрузки файлов, то все обработчики так и останутся на $rootScope

спасибо. У меня это как раз на стадии обсуждения.

Ознакомился со статьей. Что могу сказать, если речь идет о роутах, то есть события, позволяющие отловить изменения маршрута. Я так уже делал, останавливал setInteral, который крутился на странице. Т.к., если его не остановить, знаешь, что будет :)

С ui-router не работал также как и с angular route segment

Shitbox2 10.11.2013 15:24

А можно просто сделать колбеками, как у меня))

Поработай с ui-router — классная штука. Единственное, что использую от сторонних ангуляр-разработчиков) Почти всю документацию перевел

nerv_ 15.11.2013 23:29

Цитата:

Сообщение от Shitbox2
посмотри в своем загрузчике файлов, кажется у тебя там именно такая ситуация. Т.е. если я закрою страничку загрузки файлов, то все обработчики так и останутся на $rootScope

выпилил :)

Shitbox2 17.11.2013 14:34

Не очень понял смысл:
https://github.com/nervgh/angular-fi...ploader.js#L12
https://github.com/nervgh/angular-fi...ploader.js#L32
Где ты ушел от rootScope?)

nerv_ 17.11.2013 14:50

Цитата:

Сообщение от Shitbox2
Где ты ушел от rootScope?)

здесь

Shitbox2 17.11.2013 15:18

Увидел. А почему бы не брать scope из директивы? Чтобы не задавать его в контроллере всякий раз. Я бы, не зная о подвохе с rootScope, всегда бы опускал этот параметр (т.к. смысла он почти не несет) и получал бы его по-умолчанию и все связанные проблемы.

nerv_ 25.01.2014 01:24

Цитата:

Сообщение от Shitbox2
Увидел. А почему бы не брать scope из директивы? Чтобы не задавать его в контроллере всякий раз. Я бы, не зная о подвохе с rootScope, всегда бы опускал этот параметр (т.к. смысла он почти не несет) и получал бы его по-умолчанию и все связанные проблемы

потому, что я при создании экземпляра загрузчика подписываюсь на события определенной области видимости
https://github.com/nervgh/angular-fi...ploader.js#L30

nerv_ 25.01.2014 01:30

ng-repeat="value in array track by $index"
 
Уже сталкивался с подобным синтаксисом
<div ng-repeat="item in array track by $index">
   ...
</div>

и в очередной раз написав это, решил открыть доку.
В доке вижу
Цитата:

... items to be keyed by their position in the array instead of their value
Делаю вывод: это может способствовать улучшению производительности директивы ng-repeat.

---

В довесок
Цитата:

For example: item in items track by item.id is a typical pattern when the items come from the database. In this case the object identity does not matter. Two objects are considered equivalent as long as their id property is same.
http://docs.angularjs.org/api/ng.directive:ngRepeat

nerv_ 25.01.2014 01:40

$scope.$digest() instead $scope.$apply()
 
По возможности используйте
$scope.$digest(); 
// вместо
$scope.$apply();

Первый обновляет текущий и вложенные скопы, последний все приложение.

Shitbox2 28.01.2014 19:10

Думаю, в 3 пункт стоит добавить, что стоит использовать throttle или debounce для scroll, mousemove, onkeyup и т.п.

$scope.$digest() можно использовать, если есть уверенность, что изменения были только в рамках текущего скопа.

nerv_ 31.01.2014 01:45

Цитата:

Сообщение от Shitbox2
Думаю, в 3 пункт стоит добавить, что стоит использовать throttle или debounce для scroll, mousemove, onkeyup и т.п.

Только ты напиши, для чего это. Большинство таких слов ругачих не знают :) А я раскрывать секрет не стану :no:

STU 10.02.2014 10:50

Для фильтров, которые при каждом $digest'е часто возвращают одно и то же значение, полезно использовать memoize. Оно возвращает моментально значение функции, если не поменялся хэш по которому она была вычислена. Эти функции уже встроены в Underscore и Lo-Dash.
В итоге это будет выглядеть примерно так:
app.filter('tel',  [function() {
    return _.memoize(function (tel) {
	if (!tel) return '';
        tel = "("+tel.slice(0, 3)+") "+tel.slice(3, 6)+'-'+tel.slice(-4);
        return tel;
    });
}]);


По поводу вышеописанных "throttle и debounce", они так же встроены в эти библиотеки. Я просто оставлю описание с документации к Underscore:


_.throttle(function, wait) - Вернет версию функции, которая, при повторных вызовах, исполниться не чаще одного раза в заданный промежуток wait. Полезна для использования при обработке событий, которые происходят слишком часто.
var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);


_.debounce(function, wait) - Вернет версию функции, исполнение которой начнется не ранее, чем истечет промежуток wait, после ее последнего вызова. Полезно для реализации логики, которая зависит от завершения действий пользователя. Например, проверить орфографию комментария пользователя лучше будет после того, как он его окончательно введет, а динамически перерассчитать разметку после того, как пользователь закончит изменять размер окна.
var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);

STU 10.02.2014 11:04

Цитата:

Сообщение от nerv_ (Сообщение 293926)
Делаю вывод: это может способствовать улучшению производительности директивы ng-repeat.

Прошу прощения, но я не совсем понимаю как оно способствует улучшению производительности?
По-умолчанию по-моему работает логично:
If no tracking function is specified the ng-repeat associates elements by identity in the collection. It is an error to have more than one tracking function to resolve to the same key.
Переводить на стал т.к. лучше отписать оригиналом, чем ломать его своим переводом.

nerv_ 10.02.2014 11:53

Цитата:

Сообщение от STU
Переводить на стал т.к. лучше отписать оригиналом, чем ломать его своим переводом.

я сломаю:
Цитата:

If no tracking function is specified the ng-repeat associates elements by identity in the collection. It is an error to have more than one tracking function to resolve to the same key.
Если нет функция отслеживания, это указывает, что "ng-repeat" связывает элементы по идентичности в коллекции. Ошибка, если имеется более чем одна отслеживающая функция возвращающая тот же самый ключ.

Что такое "идентичность"? В рамках ангуляра отслеживание изменений по значению. А что такое отслеживание изменений по значению для массива, в котором лежат объекты? :)

та самая ошибка http://learn.javascript.ru/play/1LGDbc

тем не менее, этот пример http://learn.javascript.ru/play/Qmjpf показывает, что ангуляр пихает ключи в объекты для отслеживания (что не всегда уместно)

Проще всего исходники посмотреть или подебажить, но лениво :)

STU 10.02.2014 13:10

Цитата:

Сообщение от nerv_ (Сообщение 296888)
А что такое отслеживание изменений по значению для массива, в котором лежат объекты? :)

Спасибо, стало понятней. Фактически мы просто избавляемся от лишних вычислений, особенно если у нас массив объектов.

nerv_ 17.07.2014 16:55

$apply and $digest are both methods on AngularJS scopes that allow you to manually trigger the updates to bound properties on your scopes.

Shitbox2 21.07.2014 16:38

Черт знает. Если посмотреть исходники
if (trackByExp) {
  trackByExpGetter = $parse(trackByExp);
  trackByIdExpFn = function(key, value, index) {
  // assign key, value, and $index to the locals so that they can be used in hash functions
    if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
      hashFnLocals[valueIdentifier] = value;
      hashFnLocals.$index = index;
      return trackByExpGetter($scope, hashFnLocals);
    };
} else {
  trackByIdArrayFn = function(key, value) {
    return hashKey(value);
  };
  trackByIdObjFn = function(key) {
    return key;
  };
}

то в одном случае будет всегда срабатывать
trackByExpGetter($scope, hashFnLocals);

в другом —
hashKey(value);

Не уверен, что первая быстрее...

nerv_ 29.07.2014 16:36

Shitbox2, ты хотя бы написал, что ведешь речь о ng-repeat =)

Цитата:

Сообщение от Shitbox2
Не уверен, что первая быстрее...

ну смотри, в случае
ng-repeat="item in array"

у тебя получается, что: если item is object, то к каждому item будет впиндюрен (непереводимое слово :) $$hashKey + создана функция слежения (привет Бараку Обаме)
а в кейсе вида
ng-repeat="item in array track by item.id"

будет создана только функция слежения

---

А еще я забавный паттерн "изобрел" :D для сборки мусора:
1. в директиве
function(scope, element, attrubutes) {
    element.bind('$destroy', scope.$watch(watcher, handler));
}

2. в контроллере
function($scope) {
    $scope.$on('$destroy', $scope.$watch(watcher, handler));
}

и все счастливы =)

melky 29.07.2014 16:37

nerv_, что думаешь насчёт https://github.com/Pasvaz/bindonce ?

я глянул демку, но особо не вникал

nerv_ 29.07.2014 16:48

Цитата:

Сообщение от melky
я глянул демку

я тоже глянул. И что-то разницы не увидел =)

melky 29.07.2014 17:00

Цитата:

Сообщение от nerv_ (Сообщение 323278)
я тоже глянул. И что-то разницы не увидел =)

то бишь ввод текста в поле и там и там тормозит, или нигде не тормозит?)

... а как бы убрать лаг при добавлении 2000 записей? пагинация\бесконечный скролл?

как можно разбить добавление элементов внутри ng-repeat по итерациям setTimeout ?


не шарю :(

nerv_ 29.07.2014 22:02

Цитата:

Сообщение от melky
то бишь ввод текста в поле и там и там тормозит, или нигде не тормозит?)

тормозит везде

Цитата:

Сообщение от melky
... а как бы убрать лаг при добавлении 2000 записей? пагинация\бесконечный скролл?

не делать 2000 записей. Пагинация. Можешь взять мой модуль.
либо оптимизации

FireVolkhov 23.08.2014 09:19

1. Использовать {{::value}} синтаксис, когда нет необходимости обновлять данные (доступно в 1.3.0 beta)
Пример

2. Делать больше DOM манипуляций в директиве
Вместо
<div ng-show=”something”></div>
$scope.something = false;
$scope.someMethod = function () {
  $scope.something = true;
};

Использовать
var menu = $element.find(‘ul’);
menu.hide();
$scope.someMethod = function () {
  menu.show();
};


3. По минимуму использовать фильтры в DOM
Вместо
{{ array | filter : expression : comparator }}

Использовать
scope.result = $filter('filter')(array, expression, comparator);


4. В часто вызываемых функциях mousemove, mouseover, mouseout и т.п. не использовать лишний раз $apply или $digest

Aetae 23.08.2014 11:12

FireVolkhov, не проще тогда просто выкинуть angular на мороз и сразу писать по-человечески?)
В чём прикол фич которые всё равно нельзя использовать?)

FireVolkhov 23.08.2014 18:40

"и сразу писать по-человечески?)" это как?:blink:
Прикол фитч в том, чтобы использовать их вмеру

nerv_ 24.08.2014 00:01

Цитата:

Сообщение от FireVolkhov
1. Использовать {{::value}} синтаксис, когда нет необходимости обновлять данные (доступно в 1.3.0 beta)

не факт, что в stable будет эта возможность

Цитата:

Сообщение от FireVolkhov
2. Делать больше DOM манипуляций в директиве

Или неудачный пример... или... в чем смысл? Все равно watcher вешать и делать тоже самое, что и директивы ng-show|ng-hide. Т.е. выигрыша нет.

Цитата:

Сообщение от FireVolkhov
4. В часто вызываемых функциях mousemove, mouseover, mouseout и т.п. не использовать лишний раз $apply или $digest

уже писали)

---

Цитата:

Сообщение от Aetae
сразу писать по-человечески?)

научи =)

melky 24.08.2014 01:34

Цитата:

Сообщение от nerv_
научи =)

Node.appendChild

Node.insertAdjacentHTML

... всему вас нужно учить :)


PS вообще, ни разу ещё не страдал от фильтров в DOM (профилирование, йопт)

больше всего ненавижу ng-view (или ui-view) или ng-repeat. и ещё $watch - обычно вырубаю его , где могу (т.е. где оно не нужно... а оно не нужно много где)

FireVolkhov 24.08.2014 12:45

Цитата:

Сообщение от nerv_
Или неудачный пример... или... в чем смысл? Все равно watcher вешать и делать тоже самое, что и директивы ng-show|ng-hide. Т.е. выигрыша нет.

Да пример может не совсем удачный вот другой, суть выходит сводится к "использовать минимум watcher'ов", стараться не использовать watcher для коммуникаций внутри директивы, только для работы с другими директивами, контроллерами, сервисами.

nerv_ 24.08.2014 23:51

Цитата:

Сообщение от FireVolkhov
Да пример может не совсем удачный вот другой

При таком примере "теряется" MVC - так я не могу управлять видимостью из модели. На мой взгляд ценность такой директивы невелика)

Кроме того
content.toggleClass('ng-hide', !show);

STEVER 12.04.2015 23:07

я вот тут статью в тему перевел - "Оптимизируем AngularJS. Подробный разбор."
от Глеба Бахмутова

FireVolkhov 07.05.2015 13:49

nerv_,
Цитата:

Сообщение от nerv_
Цитата:

Сообщение от FireVolkhov
1. Использовать {{::value}} синтаксис, когда нет необходимости обновлять данные (доступно в 1.3.0 beta)

не факт, что в stable будет эта возможность

Стало частью stable можно дописывать

UPD:
Не делать многократного вложение директив в директивы, если нужна повторяющаяся логика использовать вложенные контроллеры
angular.module '...'
	.directive 'dateTextfield', ($controller, ...) ->
		link: (scope, elem, attrs) ->
			commonHintCtrl = $controller 'CommonHintCtrl',
				$scope: scope
				...

mikekelvin 18.07.2022 11:35

Angular performance optimization works well when we use lazy modules, lazy modules helps to break the large chunks of codes into small so Angular JS would be optimized.


Часовой пояс GMT +3, время: 06:35.