Angularjs的双向绑定机制带来了思维方式的转变:不再是DOM驱动,而是以Model为核心.
双向绑定机制能为我们提供很多方便的功能,但这种双向绑定需要我们时刻监控着整个页面的变化,这便是Angularjs中的脏检测机制$digest
.
由此我们不难得到此结论:越多的绑定的值需要监控,耗费在监控上的资源就越多,这也是往往导致我们Angular应用效果差的原因之一,在此我重点探讨如何去优化我们的应用,如何通过优化$watch list来提升我们应用的性能.
常见的对ng脏检查的理解是是定时轮询去检查model是否变更.
其实ng只有在指定事件触发后,才进入$digest cycle
:
提速 digestcycle是提升网页性能的关键点,而提速的途径一般是减少和尽快完成 digest,网页中的 watch便是影响 digest的关键因素.
{ { text } }
ngRepeat
,ngBind
,ngShow/ngHide
,ngIf
等下面讨论在这些产生$watch的操作如何进行优化
开发过程中有时为了实现某些业务功能,免不了需要显示使用 scope. watch函数来监控某些值的变化.
使用方法如: scope. watch(watchExpression, modelChangeCallback) , watchExpression可以是String或Function.
watch函数执行后会返回一个释放这个 watch的函数,对于那些不需要再使用到的$watch,尽早释放也会是一个不错的实践.
代码如下:
var unwatch = $scope.$watch("someKey", function(newValue, oldValue){
//do sth...
if(someCondition){
//当不需要的时候,及时移除watch
unwatch();
}
});
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);
如下,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中执行耗时操作,因为它在每次$digest都会执行1~2次.
同上,并且在此时操作DOM的价值是昂贵的.
大部分时候可能我们会在watch中操作DOM是必须的,这里提到的应该是要尽量快速少量的去操作这些DOM.
如下面的一段代码:
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数量:
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过程中的优化还有其他需要注意的方面,有时间再一一整理.
手机扫一扫
移动阅读更方便
你可能感兴趣的文章