12 January 2014

Избавляемся от лишних $watch'еров

JavaScriptAngular
Хотел бы поделиться небольшой заметкой о том, как ускорить выполнение $digest() путем замены стандартных директив эквивалентами, которые не вызывают $watch.

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

<h1 ng-bind="l10n.main_title"></h1>

<a ng-href="/edit/{{user.id}}" ng-bind="user.name"></a>

Эти и другие стандартные директивы любезно будут проверять не изменилось ли значение выражения при каждом $digest. Исправить ситуацию можно набором довольно простых кастомных директив, например для текста:

app.directive("staticText", function() {
    return {
      restrict: "A"
      link: function(scope, element, attrs) {
        element.text(scope.$eval(attrs.customText));
      }
    };
  })

<h1 static-text="l10n.main_title"></h1>

Результат — минус один $watch.
Выдумывать свой велосипед конечно же не нужно, на GitHub лежат два достойных модуля:

Первый ($watch fighters) совсем простой, включает в себя такие директивы:
  • set-title
  • set-href
  • set-text
  • set-html
  • set-class
  • set-if

Второй же (Bindonce) интереснее, т.к. разработчики подумали о том, что данные для биндинга могут быть недоступны в момент рендеринга шаблона директивы (например, они подгружаются ajax-запросом). Выглядит это так:

<div bindonce="User">
<h1 bo-text="User.name"></h1>
</div>

Для родительской директивы bindonce создается времменый $watch, когда значение переданной в неё переменной становится отличным от undefined запускаются вложенные bo-* директивы, а временный $watch удаляется.
Модуль Bindonce включает:
  • bo-if
  • bo-show
  • bo-hide
  • bo-text
  • bo-html
  • bo-href
  • bo-src
  • bo-class
  • bo-alt
  • bo-title
  • bo-id
  • bo-style
  • bo-value
  • bo-attr bo-attr-foo

Более подробно можно почитать на странице репозитория, там довольно развернутый readme.

На последок приведу функцию, которую нашел здесь. Она примерно подсчитывает общее количество $watch'еров на странице.

(function () { 
    var root = $(document.getElementsByTagName('body'));
    var watchers = [];

    var f = function (element) {
        if (element.data().hasOwnProperty('$scope')) {
            angular.forEach(element.data().$scope.$$watchers, function (watcher) {
                watchers.push(watcher);
            });
        }

        angular.forEach(element.children(), function (childElement) {
            f($(childElement));
        });
    };

    f(root);

    console.log(watchers.length);
})();

Ради интересы можно сравнить количество до и поле внедрения zero-watch директив.
Надеюсь кому-то пригодится. Спасибо.
Tags:angularjsjavascriptdirectivesdata-binding
Hubs: JavaScript Angular
+16
8k 97
Comments 4