Angularjs的双向绑定机制带来了思维方式的转变:不再是DOM驱动,而是以Model为核心.
双向绑定机制能为我们提供很多方便的功能,但这种双向绑定需要我们时刻监控着整个页面的变化,这便是Angularjs中的脏检测机制$digest
.
由此我们不难得到此结论:越多的绑定的值需要监控,耗费在监控上的资源就越多,这也是往往导致我们Angular应用效果差的原因之一,在此我重点探讨如何去优化我们的应用,如何通过优化$watch list来提升我们应用的性能.
脏数据检查 != 轮询检查更新
常见的对ng脏检查的理解是是定时轮询去检查model是否变更.
其实ng只有在指定事件触发后,才进入$digest cycle
:
- DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
- XHR响应事件 ( $http )
- 浏览器Location变更事件 ( $location )
- Timer事件( timeout, interval )
- 执行 digest()或 apply()
提速 digestcycle是提升网页性能的关键点,而提速的途径一般是减少和尽快完成 digest,网页中的 watch便是影响 digest的关键因素.
哪些操作会产生$watch?
- Angularjs中的表达式,如
{ { text } }
- Angularjs中的大部分指令,如
ngRepeat
,ngBind
,ngShow/ngHide
,ngIf
等 - 显式使用$watch函数监控数据变化
下面讨论在这些产生$watch的操作如何进行优化
开发过程中有时为了实现某些业务功能,免不了需要显示使用 scope. watch函数来监控某些值的变化.
使用方法如: scope. watch(watchExpression, modelChangeCallback) , watchExpression可以是String或Function.
及时销毁代码中的$watch
watch函数执行后会返回一个释放这个 watch的函数,对于那些不需要再使用到的$watch,尽早释放也会是一个不错的实践.
代码如下:
var unwatch = $scope.$watch("someKey", function(newValue, oldValue){
//do sth...
if(someCondition){
//当不需要的时候,及时移除watch
unwatch();
}
});
避免深度watch, 即第三个参数为true
watch的第三个参数设置为true,即可deepwatch.不过有时候其实不想或者不需要监听collection的全部属性.只要监视其中的一个或者几个,这时候通过for循环虽然可以循环 watch不过明显太挫.
通过下面这种写法就可以监控一个collection的单独一个object属性.
$scope.people = [
{
"groupname": "g1",
"persions": [
{
"id": 1,
"name": "bill"
},
{
"id": 2,
"name": "bill2"
}
]
},
{
"groupname": "g2",
"persions": [
{
"id": 3,
"name": "bill3"
},
{
"id": 4,
"name": "bill4"
}
]
}
]
$scope.$watch(function($scope) {
return $scope.people.map(function(obj) {
return obj.persions.map(function(g){
return g.name
});
});
}, function (newVal) {
$scope.count++;
$scope.msg = 'person name was changed'+ $scope.count;
}, true);
减少watch的变量长度
如下,angular不会仅对 {{value}} 建立watcher,而是对整个p标签. 双括号应该被span包裹,因为watch的是外部element
<p>plain text other {{value}} plain text other</p>
//改为:
<p>plain text other <span ng-bind='value'></span> plain text other</p>
//或
<p>plain text other <span>{{value}}</span> plain text other</p>
避免watchExpression中执行耗时操作
避免watchExpression中执行耗时操作,因为它在每次$digest都会执行1~2次.
避免watchExpression中操作dom.
同上,并且在此时操作DOM的价值是昂贵的.
大部分时候可能我们会在watch中操作DOM是必须的,这里提到的应该是要尽量快速少量的去操作这些DOM.
ngIf/ngShow ,前者会移除DOM和对应的$watch.
进行单次绑定(BindOnce)
如下面的一段代码:
html代码
<ul data-ng-controller="myController">
<li ng-repeat="people in peoples">
<div> {{people.name}} </div>
<div> {{people.age}} </div>
</li>
</ul>
js代码
angular.module('app').controller('myController', function($scope) {
$scope.peoples = [...];
});
假设以上代码中的 scope.peoples中有1000个数据,则这个页面将会产生1000∗2+1=2001个 watch,由于Angularjs的双向绑定机制,$digest会时刻监控页面中这些值的变化,很轻易的便能打到Angularjs的性能瓶颈(在社区中有这么一个经验,如果超过2000个watcher,就可能感觉到明显的卡顿,特别在IE8这种老旧浏览器上),像这种列表式的数据展示,我们通常是经常会用到的,而这些数据有往往只是一些加载进来的静态资源,双向绑定在展示上毫无用处可言,这时如果我们如果能单次绑定这些数据而不实时去监控它们的变化,网页性能是不是也一样会有所提高?
在Angularjs1.3+的版本中为Angular表达式{ { } }
引入了新语法,以“::”作为前缀的表达式为单次绑定。(单次表达式在第一次$digest完成后,将不再计算(监测属性的变化))
对于上面的例子可以改为:
<ul data-ng-controller="myController">
<li ng-repeat="people in peoples">
<div> {{::people.name}} </div>
<div> {{::people.age}} </div>
</li>
</ul>
对于1.3之前版本则可以采用一些开源项目里面封装好的一系列指令来实现(下面以Bindonce为例)
添加依赖
angular.module('com.ngnice.app', ['pasvaz.bindonce']);
添加完依赖后便可如下使用:
<ul data-ng-controller="myController">
<li bindonce ng-repeat="people in peoples">
<div bo-text="people.name"></div>
<div bo-text="people.age"></div>
</li>
</ul>
采用单次绑定的方式,页面上的每个people对象只绑定了一个 watch, watch数量从2001个缩减到1001个.
如何检查页面$watch数量
在控制台上复制下列代码执行即可看到$watch数量:
function getWatchers(root) {
root = angular.element(root || document.documentElement);
var watcherCount = 0;
function getElemWatchers(element) {
var isolateWatchers = getWatchersFromScope(element.data().$isolateScope);
var scopeWatchers = getWatchersFromScope(element.data().$scope);
var watchers = scopeWatchers.concat(isolateWatchers);
angular.forEach(element.children(), function (childElement) {
watchers = watchers.concat(getElemWatchers(angular.element(childElement)));
});
return watchers;
}
function getWatchersFromScope(scope) {
if (scope) {
return scope.$$watchers || [];
} else {
return [];
}
}
return getElemWatchers(root);
}
getWatchers().length;
或者安装chrome下的Angularjs拓展AngularJS Batarang也可以实时查看页面的$watch
数量.
总结
Angularjs的双向绑定机制是建立在 digest脏检测基础上的,而 watch的数量是影响 digest的主要因素之一,在实践中移除不必要的 watch对应用性能的提升不容小觑,当页面上的$watch数量过多时,思考如果减少watch数量将会是一个不错的方向.
有关Angularjs中$digest过程中的优化还有其他需要注意的方面,有时间再一一整理.