Поиск…


7 Простые улучшения производительности

1) Используйте ng-repeat экономно

Использование ng-repeat во взглядах обычно приводит к низкой производительности, особенно при наличии вложенных ng-repeat .

Это супер медленно!

<div ng-repeat="user in userCollection">
  <div ng-repeat="details in user">
    {{details}}
  </div>
</div>

Старайтесь избегать вложенных повторов как можно больше. Одним из способов улучшить производительность ng-repeat является использование track by $index (или другому полю id). По умолчанию ng-repeat отслеживает весь объект. С помощью track by Angular наблюдает за объектом только по $index или object.

<div ng-repeat="user in userCollection track by $index">
  {{user.data}}
</div>

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


2) Привязать один раз

Угловое имеет двунаправленную привязку данных. Это связано с тем, что он слишком медленный, если использовать слишком много.

Более низкая производительность

<!-- Default data binding has a performance cost -->
<div>{{ my.data }}</div>

Более высокая производительность (AngularJS> = 1,3)

<!-- Bind once is much faster -->
<div>{{ ::my.data }}</div>

<div ng-bind="::my.data"></div>

<!-- Use single binding notation in ng-repeat where only list display is needed  -->
<div ng-repeat="user in ::userCollection">
  {{::user.data}}
</div>

Используя нотацию «привязать один раз», Angular ожидает, что значение будет стабилизироваться после первой серии циклов дайджеста. Угловой будет использовать это значение в DOM, а затем удалить всех наблюдателей, чтобы он стал статическим значением и больше не привязан к модели.

{{}} Намного медленнее.

Это ng-bind является директивой и помещает наблюдателя в переданную переменную. Таким образом, ng-bind будет применяться только тогда, когда переданное значение действительно изменится.

Скобки, с другой стороны, будут грязно проверены и обновлены в каждом $digest , даже если это не обязательно.


3) Функции области и фильтры требуют времени

У AngularJS есть цикл дайджеста. Все ваши функции находятся в режиме просмотра, и фильтры выполняются каждый раз, когда цикл дайджест выполняется. Цикл дайджеста будет выполняться всякий раз, когда модель обновляется, и это может замедлить ваше приложение (фильтр может быть удален несколько раз до загрузки страницы).

Избегайте этого:

<div ng-controller="bigCalulations as calc">
  <p>{{calc.calculateMe()}}</p>
  <p>{{calc.data | heavyFilter}}</p>
</div>

Лучший подход

<div ng-controller="bigCalulations as calc">
  <p>{{calc.preCalculatedValue}}</p>
  <p>{{calc.data | lightFilter}}</p>
</div>

Где контроллер может быть:

app.controller('bigCalulations', function(valueService) {
    // bad, because this is called in every digest loop
    this.calculateMe = function() {
        var t = 0;
        for(i = 0; i < 1000; i++) {
            t += i;
        }
        return t;
    }
    // good, because this is executed just once and logic is separated in service to    keep the controller light
    this.preCalulatedValue = valueService.valueCalculation(); // returns 499500
});

4) Наблюдатели

Наблюдатели сильно понижают производительность. С большим количеством наблюдателей цикл digest займет больше времени, и пользовательский интерфейс будет замедляться. Если наблюдатель обнаруживает изменения, он начнет цикл дайджеста и повторно отобразит представление.

Существует три способа ручного просмотра переменных изменений в Angular.

$watch() - часы для изменения стоимости

$watchCollection() - часы для изменений в коллекции (часы больше, чем обычные $watch )

$watch(..., true) - Избегайте этого как можно больше, он будет выполнять «глубокие часы» и watchCollection производительность (часы больше, чем watchCollection )

Обратите внимание: если вы привязываете переменные в представлении, вы создаете новые часы - используйте {{::variable}} чтобы предотвратить создание часов, особенно в циклах.

В результате вам нужно отслеживать, сколько наблюдателей вы используете. Вы можете подсчитать наблюдателей с помощью этого сценария (кредит для @Words Like Jared Количество наблюдателей )

(function() {
    var root = angular.element(document.getElementsByTagName('body')),
        watchers = [],
        f = function(element) {
        angular.forEach(['$scope', '$isolateScope'], function(scopeProperty) {
            if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
                angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
                watchers.push(watcher);
                });
            }
        });

        angular.forEach(element.children(), function(childElement) {
            f(angular.element(childElement));
        });
    };
 
    f(root);
 
    // Remove duplicate watchers
    var watchersWithoutDuplicates = [];
    angular.forEach(watchers, function(item) {
        if(watchersWithoutDuplicates.indexOf(item) < 0) {
            watchersWithoutDuplicates.push(item);
        }
    });
    console.log(watchersWithoutDuplicates.length);
})();

5) ng-if / ng-show

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

Это зависит от типа использования, но часто один из них более подходит, чем другой.

  • Если элемент не нужен, используйте ng-if

  • Чтобы быстро включить / выключить, используйте ng-show/ng-hide

    <div ng-repeat="user in userCollection">
      <p ng-if="user.hasTreeLegs">I am special<!-- some complicated DOM --></p>
      <p ng-show="user.hasSubscribed">I am awesome<!-- switch this setting on and off --></p>
    </div>
    

Если есть сомнения - используйте ng-if и test!


6) Отключить отладку

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

angular.module('exampleApp', []).config(['$compileProvider', function ($compileProvider) {
    $compileProvider.debugInfoEnabled(false);
}]);

7) Используйте инъекцию зависимостей, чтобы разоблачить ваши ресурсы

Dependency Injection - это шаблон разработки программного обеспечения, в котором объекту присваиваются его зависимости, а не сам объект, создающий их. Речь идет об удалении жестко закодированных зависимостей и их изменении при необходимости.

Вы можете задаться вопросом о стоимости выполнения, связанной с таким синтаксическим разбором всех инъекционных функций. Угловые заботятся об этом путем кэширования $ inject-свойства после первого раза. Так что это не происходит каждый раз, когда нужно вызвать функцию.

PRO TIP: Если вы ищете подход с наилучшей производительностью, перейдите к подстроке аннотаций $ injection. Этот подход полностью исключает синтаксический анализ определения функции, поскольку эта логика завершается в следующей проверке функции аннотации: if (! ($ Inject = fn. $ Inject)). Если $ inject уже доступен, синтаксический анализ не требуется!

var app = angular.module('DemoApp', []);

var DemoController = function (s, h) {
    h.get('https://api.github.com/users/angular/repos').success(function (repos) {
        s.repos = repos;
    });
}
// $inject property annotation
DemoController['$inject'] = ['$scope', '$http'];

app.controller('DemoController', DemoController);

PRO TIP 2: вы можете добавить директиву ng-strict-di в том же элементе, что и ng-app чтобы перейти в строгий режим DI, который будет вызывать ошибку всякий раз, когда служба пытается использовать неявные аннотации. Пример:

<html ng-app="DemoApp" ng-strict-di>

Или если вы используете ручную загрузку:

angular.bootstrap(document, ['DemoApp'], {
    strictDi: true
});

Bind Once

Угловая имеет репутацию за то, что у нее потрясающая двунаправленная привязка данных. По умолчанию «Угловая» непрерывно синхронизирует значения, связанные между моделью и компонентами представления, при любых изменениях данных времени как в модели, так и в представлении.

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

Плохая производительность: {{my.data}}

Добавьте два двоеточия :: перед именем переменной используйте одноразовую привязку. В этом случае значение обновляется только после определения my.data. Вы явно указываете, что не следите за изменениями данных. Угловой не будет выполнять никаких проверок значений, в результате чего меньшее количество выражений оценивается в каждом цикле дайджеста.

Хорошие примеры производительности с использованием одноразовой привязки

{{::my.data}}
<span ng-bind="::my.data"></span>
<span ng-if="::my.data"></span>
<span ng-repeat="item in ::my.data">{{item}}</span>
<span ng-class="::{ 'my-class': my.data }"></div>

Примечание. Однако это устраняет двунаправленную привязку данных для my.data , поэтому всякий раз, когда это поле изменяется в вашем приложении, оно не будет автоматически отражено в представлении. Поэтому используйте его только для значений, которые не будут меняться на протяжении всего срока действия вашего приложения .

Функции и фильтры области

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

Вам следует избегать этого:

<div ng-controller="bigCalulations as calc">
  <p>{{calc.calculateMe()}}</p>
  <p>{{calc.data | heavyFilter}}</p>
</div>

Лучший подход

<div ng-controller="bigCalulations as calc">
  <p>{{calc.preCalculatedValue}}</p>
  <p>{{calc.data | lightFilter}}</p>
</div>

Где контрольный образец:

.controller("bigCalulations", function(valueService) {
  // bad, because this is called in every digest loop
  this.calculateMe = function() {
    var t = 0;
    for(i = 0; i < 1000; i++) {
      t = t + i;
    }
    return t;
  }
  //good, because it is executed just once and logic is separated in service to    keep the controller light
  this.preCalulatedValue = valueService.caluclateSumm(); // returns 499500
});

Зрителей

Наблюдателям необходимо следить за некоторой стоимостью и обнаруживать, что это значение изменено.

После вызова $watch() или $watchCollection новый наблюдатель добавит во внутреннюю коллекцию наблюдателей в текущей области.

Итак, что такое наблюдатель?

Watcher - простая функция, которая вызывается для каждого цикла дайджеста и возвращает некоторое значение. Угловой проверяет возвращаемое значение, если оно не совпадает с предыдущим вызовом - будет выполнен обратный вызов, который был передан во втором параметре для функции $watch() или $watchCollection .

(function() {
  angular.module("app", []).controller("ctrl", function($scope) {
    $scope.value = 10;
    $scope.$watch(
      function() { return $scope.value; },
      function() { console.log("value changed"); }
    );
  }
})();

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

Существует три способа ручного просмотра для переменных изменений в Угловом.

$watch() - просто наблюдает за изменениями стоимости

$watchCollection() - часы для изменений в коллекции (часы больше, чем обычные $ watch)

$watch(..., true) - Избегайте этого как можно больше, он будет выполнять «глубокие часы» и убьет производительность (часы больше, чем watchCollection)

Обратите внимание: если вы привязываете переменные в представлении, вы создаете новых наблюдателей - используйте {{::variable}} чтобы не создавать наблюдателей, особенно в циклах

В результате вам нужно отслеживать, сколько наблюдателей вы используете. Вы можете подсчитать наблюдателей с помощью этого сценария (кредит на @Words Like Jared). Как подсчитать общее количество часов на странице?

(function() {
  var root = angular.element(document.getElementsByTagName("body")),
      watchers = [];

  var f = function(element) {

    angular.forEach(["$scope", "$isolateScope"], function(scopeProperty) {
      if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
        angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
          watchers.push(watcher);
        });
      }
    });

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

  };

  f(root);

  // Remove duplicate watchers
  var watchersWithoutDuplicates = [];
  angular.forEach(watchers, function(item) {
    if(watchersWithoutDuplicates.indexOf(item) < 0) {
      watchersWithoutDuplicates.push(item);
    }
  });

  console.log(watchersWithoutDuplicates.length);

})();

Если вы не хотите создавать свой собственный скрипт, есть утилита с открытым исходным кодом, называемая ng-stats, которая использует диаграмму реального времени, встроенную в страницу, чтобы дать вам представление о количестве часов, которыми управляет Angular, а также частоты и продолжительности циклов дайджеста с течением времени. Утилита предоставляет глобальную функцию с именем showAngularStats которую вы можете вызвать, чтобы настроить, как вы хотите, чтобы график работал.

showAngularStats({
  "position": "topleft",
  "digestTimeThreshold": 16,
  "autoload": true,
  "logDigest": true,
  "logWatches": true
});

В приведенном выше примере приведена следующая диаграмма на странице ( интерактивная демонстрация ).

скриншот диаграммы ng-stats

ng-if vs ng-show

Эти функции очень похожи в поведении. Разница в том, что ng-if удаляет элементы из DOM. Если есть большие части кода, которые не будут показаны, то ng-if - путь. ng-show только скроет элементы, но сохранит все обработчики.

нг-если

Директива ngIf удаляет или воссоздает часть дерева DOM на основе выражения. Если выражение, присвоенное ngIf, оценивается как ложное значение, тогда элемент удаляется из DOM, иначе клон элемента снова вводится в DOM.

нг-шоу

Директива ngShow показывает или скрывает данный HTML-элемент на основе выражения, предоставляемого атрибуту ngShow. Элемент отображается или скрывается удалением или добавлением класса CSS ng-hide в элемент.

пример

<div ng-repeat="user in userCollection">
  <p ng-if="user.hasTreeLegs">I am special
    <!-- some complicated DOM -->
  </p>
  <p ng-show="user.hasSubscribed">I am aweosme
    <!-- switch this setting on and off -->
  </p>
</div>

Заключение

Это зависит от типа использования, но часто один из них более подходит, чем другой (например, если в 95% случаев элемент не нужен, используйте ng-if , если вам нужно переключить видимость элемента DOM, используйте ng-show ).

Если вы сомневаетесь, используйте ng-if и test!

Примечание : ng-if создает новую изолированную область, в то время как ng-show и ng-hide - нет. Используйте $parent.property если свойство родительской области недоступно в нем.

Откажитесь от своей модели

<div ng-controller="ExampleController">
    <form name="userForm">
        Name:
        <input type="text" name="userName"
           ng-model="user.name"
           ng-model-options="{ debounce: 1000 }" />
            <button ng-click="userForm.userName.$rollbackViewValue();   user.name=''">Clear</button><br />
    </form>
    <pre>user.name = </pre>
</div>

В приведенном выше примере мы устанавливаем значение дебюта 1000 миллисекунд, которое составляет 1 секунду. Это значительная задержка, но предотвратит неоднократное измельчение ввода ng-model со многими циклами $digest .

Используя debounce в ваших полях ввода и где-либо еще, где мгновенное обновление не требуется, вы можете существенно увеличить производительность ваших приложений с угловым выражением. Вы не только можете задерживаться по времени, но также можете задерживать время срабатывания. Если вы не хотите обновлять свою ng-модель при каждом нажатии клавиши, вы также можете обновить ее.

Всегда отменять регистрацию слушателей, зарегистрированных в других областях, отличных от текущей области

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

//always deregister these
$rootScope.$on(...);
$scope.$parent.$on(...);

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

//no need to deregister this
$scope.$on(...);

$rootScope.$on слушателях останется в памяти, если вы перейдете к другому контроллеру. Это приведет к утечке памяти, если контроллер выпадет из области видимости.

не

angular.module('app').controller('badExampleController', badExample);
badExample.$inject = ['$scope', '$rootScope'];

function badExample($scope, $rootScope) {
    $rootScope.$on('post:created', function postCreated(event, data) {});
}

Делать

angular.module('app').controller('goodExampleController', goodExample);
goodExample.$inject = ['$scope', '$rootScope'];

function goodExample($scope, $rootScope) {
    var deregister = $rootScope.$on('post:created', function postCreated(event, data) {});

    $scope.$on('$destroy', function destroyScope() {
        deregister();
    });
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow