Angularjs实践之优化你的$watch
2015-09-08 02:50
591 查看
Angularjs的双向绑定机制带来了思维方式的转变:不再是DOM驱动,而是以Model为核心.
双向绑定机制能为我们提供很多方便的功能,但这种双向绑定需要我们时刻监控着整个页面的变化,这便是Angularjs中的脏检测机制
由此我们不难得到此结论:越多的绑定的值需要监控,耗费在监控上的资源就越多,这也是往往导致我们Angular应用效果差的原因之一,在此我重点探讨如何去优化我们的应用,如何通过优化$watch list来提升我们应用的性能.
其实ng只有在指定事件触发后,才进入
DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
XHR响应事件 ( $http )
浏览器Location变更事件 ( $location )
Timer事件( timeout,interval )
执行 digest()或apply()
提速 digestcycle是提升网页性能的关键点,而提速的途径一般是减少和尽快完成digest,网页中的watch便是影响digest的关键因素.
Angularjs中的大部分指令,如
显式使用$watch函数监控数据变化
下面讨论在这些产生$watch的操作如何进行优化
开发过程中有时为了实现某些业务功能,免不了需要显示使用scope.watch函数来监控某些值的变化.
使用方法如:scope.watch(watchExpression, modelChangeCallback) , watchExpression可以是String或Function.
代码如下:
通过下面这种写法就可以监控一个collection的单独一个object属性.
大部分时候可能我们会在watch中操作DOM是必须的,这里提到的应该是要尽量快速少量的去操作这些DOM.
假设以上代码中的 scope.peoples中有1000个数据,则这个页面将会产生1000∗2+1=2001个watch,由于Angularjs的双向绑定机制,$digest会时刻监控页面中这些值的变化,很轻易的便能打到Angularjs的性能瓶颈(在社区中有这么一个经验,如果超过2000个watcher,就可能感觉到明显的卡顿,特别在IE8这种老旧浏览器上),像这种列表式的数据展示,我们通常是经常会用到的,而这些数据有往往只是一些加载进来的静态资源,双向绑定在展示上毫无用处可言,这时如果我们如果能单次绑定这些数据而不实时去监控它们的变化,网页性能是不是也一样会有所提高?
在Angularjs1.3+的版本中为Angular表达式
对于上面的例子可以改为:
对于1.3之前版本则可以采用一些开源项目里面封装好的一系列指令来实现(下面以Bindonce为例)
Bindonce
Angular-once
添加完依赖后便可如下使用:
采用单次绑定的方式,页面上的每个people对象只绑定了一个watch,watch数量从2001个缩减到1001个.
或者安装chrome下的Angularjs拓展AngularJS Batarang也可以实时查看页面的
有关Angularjs中$digest过程中的优化还有其他需要注意的方面,有时间再一一整理.
Angular性能优化心得
双向绑定机制能为我们提供很多方便的功能,但这种双向绑定需要我们时刻监控着整个页面的变化,这便是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移除不必要的$watchAngular性能优化心得
相关文章推荐
- angularJS 中$attrs方法使用指南
- 简述AngularJS相关的一些编程思想
- AngularJS的一些基本样式初窥
- 创建你的第一个AngularJS应用的方法
- 详解AngularJS中的作用域
- 简介AngularJS的视图功能应用
- AngularJS语法详解
- angularjs中的e2e测试实例
- 简介AngularJS中使用factory和service的方法
- AngularJS + Node.js + MongoDB开发的基于高德地图位置的通讯录
- 举例讲解AngularJS中的模块
- 使用AngularJS处理单选框和复选框的简单方法
- AngularJS+Node.js实现在线聊天室
- AngularJS的内置过滤器详解
- 详解AngularJS中自定义指令的使用
- 使用Raygun来自动追踪AngularJS中的异常
- angularJS结合canvas画图例子
- 教你用AngularJS框架一行JS代码实现控件验证效果
- AngularJS入门教程(零):引导程序
- AngularJS基础学习笔记之简单介绍