您的位置:首页 > Web前端 > AngularJS

Angularjs实践之优化你的$watch

2015-09-08 02:50 591 查看
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为例)

Bindonce

Angular-once

添加依赖

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过程中的优化还有其他需要注意的方面,有时间再一一整理.

参考资料

破狼-angularjs移除不必要的$watch

Angular性能优化心得
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  angularjs