Pull to refresh

Comments 31

$scope.myVar = 2 * 2;
$scope.$watch('myVar', function() {
	alert('myVar has changed!');
});

'myVar' будет высчитываться минимум один-два раза при каждом цикле $digest.

'myVar' будет высчитываться только один раз, а вот проверка изменений $scope.myVar будет происходить, конечно, в каждом цикле.
Теперь $watch будет следить за ссылкой на функцию, а не за значением её выполнения. Тогда уж надо

$scope.$watch(function() {
   return $scope.myVar();
}, function() {
   alert('myVar has changed!');
});

нет, не за ссылкой) все верно в примере написано.
Условно говоря, время выполнения цикла $digest = количество наблюдателей * время их выполнения.
Можно умножить на кол-во циклов, что-б получить время выполнения $digest, т.к. при плохом использовании $watch, можно увеличить кол-во циклов в каждом $digets.

используйте ng-switch, чтобы удалить скрытые элементы из самого DOM.
Так же можно использовать ng-if

Каждый фильтр в AngularJS выполняется минимум один-два раза при каждом цикле $digest
Не один-два, а ровно один раз в каждом цикле, и то если это stateful фильтр. А вызов (обычного) «stateless» фильтра будет зависеть — изменилось ли входящее значение.

Сократите количество обращений к серверу, модифицировав передаваемые данные.
Так же можно использовать useApplyAsync

Так же не сказано про самую главную «оптимизацию» — компонентный подход (сейчас это как рекомендация для многих фреймворков*), таким образом можно, например, 100к ватчей разбить* на 100 компонентов по ~1000 ватчей в каждом, таким образом $digest цикл будет обрабатывать не 100К а всего лишь 1000 ватчей, а это быстрее чем 1мс (для простых ватчей).
Так же не сказано про самую главную «оптимизацию» — компонентный подход

Не могли бы вы привести пример?
Например если взять комментарии с хабра, каждый комментарий имеет имя автора, аватарку, время, контент, голоса и кнопки изменения рейтинга, на стороне редактирования несколько контролов с таймером. Главное что каждый комментарий более-менее независим от окружающих. Конечно большинство биндингов могут быть bindOnce или oneTimeBinding, но если на вскидку взять 30 ватчей на весь функционал одного комментария, то на странице с 300 комментариями будет 9000 ватчей — не так и мало, особенно если $digest будет вызываться на каждое нажатие кнопки (при написании комментария, например).

Дак вот если сделать каждый комментарий как «независимый» компонент (директива), со своим (root) scope, то при работе с одним комментарием, будет дергаться $digest только на 30 ватчей — и это будет работать быстро, несмотря на кол-во комментарий на странице.

Так же Angular 2 «вводит» компоненты (хотя я не знаю на сколько они изолированы), для Angular 1 разработчики создают rootScope под каждый компонент (возможно в виде директивы).

Вот 2 одинаковых примера на Angular Light — список блоков с контролами, только в одном используется scope: 'root' — т.е. каждый блок имеет свой (root) scope: пример с общим scope, пример где каждый блок имеет отдельный scope, если покликать на кнопки то видно что в первом примере любые действия влияют на все приложение — добавляете watch в один блок, тем самым он нагружает все остальные блоки, когда во втором примере этого не происходит.

Так же такие элементы как верхнее меню, выскакивающие диалоги, блоки с рекламой, «Популярное за сутки» и пр. никак не связаны с комментариями и главным контентом страницы, все их имеет смысл делать самостоятельными компонентами, а не «кусочком» монолитного приложения. Это не только «ускорят» приложение но и хорошо для архитектуры приложения, имхо.
я не знаю, что там происходит с Angular Light, но в обычном Angular'е, никаких дополнительных $rootScope на каждую директиву не создается. $rootScope это вообще сервис, который по определению синглтон в ангуляре. Isolated scope это другое. В производительности можно выиграть, если вызывать $digest на этом скоупе, вместо $apply ($rootScope.$digest). Но это может быть не всегда возможно.
Angular'е, никаких дополнительных $rootScope на каждую директиву не создается
Штатно не создается, но можно сделать новый rootScope вручную (не через сервис), и у него будет свой $digest цикл. Правильно это или нет, каждый принимает решение за себя, но некоторые это используют.
А можно пример как это сделать?
Например так:
newRootScope = new $rootScope.constructor()

Хотя я видел более красивый вариант.
А что это дает в сравнении со $scope.$new() с вызовом $digest на нем? В чайлдовых директивах и темплейтах все равно будет использоваться то что возвращает $injector сервис. Вся целостность приложения ломается. Либо я чего-то не понимаю.
да вот только это три отдельных приложения.
Главное работает, дальше можно сделать красивые обертки и использовать.
А вообще, апи у Angular 1 в этом плане не удачное, и то что в Angular 2 делают по другому — это как подтверждение.
Черт, естественно оно работает, чего б ему не работать :) Вот только
Вся целостность приложения ломается

Как вы в таком случае намереваетесь налаживать какое-то взаимодействие между этими компонентами? И, главное, зачем так извращаться? Если приходиться это делать чтобы сократить количество watcher'ов, то тут проще вообще от data-binding'а в шаблонах избавиться, чем по инстансу приложения создавать на контрол. Про удачность апи это да.
Как вы в таком случае намереваетесь налаживать какое-то взаимодействие между этими компонентами?
Я не намереваюсь, я использую Angular Light и у меня таких проблем нет.
Про удачность апи это да. ждем 2-й ангуляр :)
Ангуляр 2 ещё не зарелизился, а в нем уже не все хорошо как хотелось бы, хотя фанатам буде плевать. Так же кроме Ангуляра есть полно других фреймворков (и не плохих тоже).
Я не намереваюсь, я использую Angular Light и у меня таких проблем нет.

Ну так спорили же за обычный. Идеальных фреймворков вообще не бывает, посмотрим чего они там намудрят…
Спасибо за ценный комментарий.

Подскажите, пожалуйста, по аргументам в ваших примерах: scope:'root'/'isolate' — в документации написано, что аргументы могут быть true/'isolate', но в ваших примерах используется 'root', который ведет себя как-то по-особенному, а все остальное (true, 'isolate', 'foo') ведет одинаково.
Вот новая дока (с главной страницы на неё ссылка).
А вообще почти все параметры для директив — это просто ф-ии, и можно добавить своих аттрибутов, вот тот самый «обработчик» scope или в js виде.
(true, 'isolate', 'foo') ведет одинаково.
— Если scope не указан, то используется родительский scope.
— Если true (или любое другое, что приводится к true, например 'foo'), тогда создается дочерний scope где родитель = прототип, т.е. все данные/методы родителя доступны в новом scope.
— Если 'isolate' тогда, создается дочерний, чистый scope, где родитель в scope.$parent, в самом scope не видно данных и методов родителя.
— Если 'root', то создается отдельный (root) scope, и «монтируется» к родительскому scope, при этом родитель в scope.$parent
выполняется минимум один-два раза
Как-то очень туманно. Ценность такой информации под вопросом. Сам я всегда проверяю сколько раз выполняются фильтры и т.п. Выполняется столько сколько нужно, никаких скрытых повторных выполнений не встречал.
Это зависит от криворукости написанного приложения.
Если прочитать доку, то там будет сказано, что ватчер проверяется минимум два раза. Вторая проверка делается для того, чтобы обыграть случай, когда после первого прохода один из ватчеров изменяет переменную, находящуюся под этим или другим ватчем.
ватчер проверяется минимум два раза
Это так, хотя зачастую достаточно и одного прохода, например ватчи которые создаются декларативным {{биндингом}} или если подключить контрол input — в них ватчи используется только для отслеживания изменений, и они не меняют модель (в input-e ватч не меняет модель, это делает директива вне ватча). А раз они не меняют модель, то и второй проход $digest не нужен, именно так и работает в Angular Light — если сделать форму с input и {{биндингом}}, то видно что при вводе текста происходит только один проход.
Эм и все? Где «рабочие примеры»? Рассказали бы об one-time bindings (::), которые появились в 1.3, позволяет значительно повысить перфоманс, уменьшив количество вотчеров.
Вот только «one-time» == «одноразовый» («однократный»), а не «односторонний». Одностороннее связывание — это one-way binding.

Например, ng-bind=«vm.text», {{vm.text}}, ng-bind="::vm.text", {{::vm.text}} — это всё односторонние связки (от модели к представлению, но не наоборот), но из них только две последних — одноразовые.
Коррективы:
1. В вашем примере:
$scope.myVar = function() {
return 2 * 2;
}
$scope.$watch('myVar', function() {
alert('myVar has changed!');
});

myVar — будет отслеживаться по «ссылке» на функцию а не по возвращаемому значению!
для вашей цели нужно писать:
$scope.$watch(myVar, ....); // без кавычек
либо
$scope.$watch('myVar()', ....); // указывать вызов функции

2. При «stateful» фильтр (а он по умолчанию такой) в digest-цикле проверяется только значение аргументов и не выполняется расчет фильтра, если они остались неизменными.

Еще хорошее средство для сокращения числа «вотчеров» в ngRepeat:
1. сделать «директиву-обертку» для каждого элемента списка, в параметрах, которого вместо (к примеру 10 параметров) передается объект содержащий эти параметры — это позволяет уменьшить количество «вотчеров» в разы ( сравнимое с количеством объединенных в объект параметров).
2. для тех параметров из объекта, значение которых нужно отслеживать ставить внутри этой директивы «вотчер» с запуском $scope.$digest вместо $scope.$apply, а порядок запуска регулировать c помощью $timeout( fn, 0, false) — это в ряде случаев с множественными ngRepeat — позволяет еще в разы уменьшить количество обходов «вотчеров» при частных обновлениях

Еще для своих проектов писали ряд директив автоматически делающих это, плюс запуск «вотчеров» «по требованию» ( по событию ) — тоже очень здорово разгружает когда редкие обновления или ненужно двустороннее связывание
Sign up to leave a comment.

Articles