Serenader

Learning by sharing

Angular 性能优化

尽量减少 Watcher 的数量:

以下代码均会产生 watcher:
  • $scope.$watch
  • {{}} 数据绑定
  • 大多数 ng directive,如 ng-showng-hide
  • $scope 变量的属性及方法,如 $scope.method = function () {}
  • DOM filter,如 {{ title | greet}}
  • ng-repeat wacher 将会存储在各自相应的 $scope.$$wachers 里面。当有新的 wacher 产生时,则会自动添加到 $scope.$$acher 里面。
watcher 在以下情况下会运行(digest cycle):
  • 用户的操作,如 ng-click 等。大多数内置的 directive 都会调用 $scope.$apply 来实现 digest cycle.
  • ng-change
  • ng-model
  • $http 请求事件
  • $q promise 对象的 resolve
  • $timeout
  • $interval
  • 手动的调用 $scope.$digest 以及 $socpe.$apply 当执行 digest cycle 时,会执行 $scope.$$wachers 里面的所有回调函数。当某个 wacher 函数改变了某个被监听了的 model 时,则会重新执行 digest cycle ,直到数据稳定不再变化为止。

1、使用 One-Time binding

1.3.0 版本开始,Angular 开始支持 One-Time Binding ,即 model 只会被计算一次,当计算完成之后则会删除该 model 的所有 watcher ,从而起到性能优化的作用。
// 传统写法
<p>{{ title }}</p>

// One-Time binding
<p>{{ ::title }}</p>

2、$scope.$digest$scope.$apply

$digest$apply 均可以触发 Angular 的 digest loop ,唯一不同的是, $apply 会触发 $rootScope.$digest() ,而,$digest 只会触发当前 $scope$scope.$digest() ,因此,$apply 会重新计算页面所有的 $scope 的 model,而 $digest 只会重新计算当前 $scope 以及它的子 scope。 当需要让 Angular 重新计算的 model 是位于当前 scope 里面时,应该使用 $digest ,避免使用 $apply

3、尽量减少在 DOM 使用 filter

DOM里面的每一个filter 在每次 $digest loop 里里面至少会执行两次,因此,如果项目一旦稍具规模的话,filter 的开销是非常大的。 相对于在 DOM 里面使用 filter ,可以先在 JS 里面,使用 Angular 提供的 $filter 服务,先将 model 转化一下,再将它渲染在 DOM 里面:
// DOM 里面的 filter:
<div> {{filter_expression | filter : expression : comparator}} </div>
// 例如:
<div> {{ currentTime | time }} </div>

// JS 里面的 filter:

$filter('filter')(array, expression, comparator);

// 例如:
angular.module('app').controller('ctrl', function ($scope, $filter) {
    var currentTime = new Date();
    $scope.currentTime = $filter('time')(currentTime);
});

4、可以使用 $watchCollection 来取代 深度的 $watch

Angular 的 $watch 有两种调用方式,一种是传递两个参数,一种是传递三个参数。第三个参数表示是否执行深度检查。深度检查意味着 Angular 会检查该对象的每一个属性是否发生变化。 而 $watchCollection 在大部分时候和 $watch 的深度检查一样,只不过,$watchCollection 指检查当前对象的第一层属性。而不像 $watch 的深度检查一样,会检查所有的属性。

5、设置 ngModeldebounce

ng-model-option="{debounce: 250}" 这一行配置会告诉 Angular,当 model 的数据在 250 毫秒之内没有发生变化的时候,才会更新 digest cycle 。比如可以用在搜索框,使得用户的输入与实时请求搜索结果有一个250毫秒的延迟。

6、如果 ng-if 更加适合的话,尽量使用 ng-if 取代 ng-show / ng-hide

ng-showng-hide 实质只是为元素添加一个 ng-hide 的类,并且强制设置样式为 display: none 。尽管可以实现元素的隐藏,但是其内部的 watcher 仍然存在,仍然会在每次 digest cycle 进行计算。倘若当元素隐藏之后,没有必要再计算元素内部的 model 时,可以使用 ng-ifng-if 会把整个元素从 DOM 中移除,并且删除里面的 watcher 。

7、不要将函数绑定给 ng-show ,以及 $scope.$watch 传递函数的返回值