项目中Angularjs遇到的问题和优化总结
由于本项目最低需要兼容ie8浏览器,所以在版本选择上选择Angularjs1.2版本。
1.ng-if/ng-switch与ng-show/ng-hide区别选择
- ng-show/ng-hide是通过修改CSS样式方式控制元素显示与隐藏,对应的DOM元素会一直存在于当前页面中,本质是CSS属性操作display:none;display:block,而ng-if根据表达式的值动态的在当前的页面中添加删除页面元素。如果赋值表达式的值为false,那么这个元素就会从页面中删除,否则会添加一个元素。ng-if创建元素时用的是被它编译后的代码,如果ng-if内部的代码被其它方式修改过,那么修改只会对本次展现有效,页面元素重新渲染后修改效果会消失,而ng-show/ng-hide则能够保留dom元素上次修改后的状态。
- 在作用域方面,两者也存在差异:当一个元素被ng-if从DOM中删除时,与其关联的作用域也会被销毁。而且当它重新加入DOM中时,则会生成一个新的作用域,而ng-show和ng-hide则不会。
项目中由于需要多个列表数据较多,如果频繁的重建和删除DOM元素,将十分耗性能,所以在性能考虑上使用ng-show。
当然也有另一种情况,那就是一些长列表数据,可能有一些东西是通过默认隐藏,点击显示的形式展现的.而这部分可控制显隐的内容中也会伴随很多数据绑定.这个在页面渲染的时候非常影响性能.(angular建议一个页面的数据绑定不超过2000个,假如现在有一个页面直接绑定了2000个model,然后你加载,会发现非常卡.如果你将每100的model设置为ng-show,默认情况下不显示,你会发现还是很卡.)然后你将所有的ng-show换成ng-if,你会发现性能瞬间快的像两个应用.原因在ng-show还是会执行其中的所有绑定,ng-if则会在等于true,也就是显示的时候再去执行其中的绑定.这样一来性能就有很大的提高.
ng-show = false
ng-show=true
ng-if = true
ng-if = false
angular.module(“app”,[]).controller(“MainCtrl”,function($scope){
});
2 双向数据绑定{{}}使用刷新出现{{}}
由于后台数据问题没有访问成功,前台界面就不需要显示{{}},用ng-bind指令代替{{}}
3
rootScrope以及和
scope的区别?
-
rootScrope页面所有 scope的父亲。 - 如何产生
rootScope和 scope
step1:Angular解析ng-app然后在内存中创建$rootScope。
step2:angular回继续解析,找到{{}}表达式或者ng-bind指令,并解析成变量。
step3:接着会解析带有ng-controller的div然后指向到某个controller函数。这个时候在这个controller函数变成一个$scope对象实例。
4 使用路由 去除url中总是默认带有”#”
在设置route的时候,开启HTML5模式.
angular.module('router',['ngRoute']) .config(['$routeProvider','$locationProvider',function($routeProvider,$locationProvider) { $locationProvider.html5Mode(true); // 设置一下这句即可 } ]);
5 页面快速定位元素位置
一般来讲页面内通过这样的形式就可以结合js代码,实现快速定位.在angular中也是通过类似的原理实现,代码如下:
var old = $location.hash(); $location.hash('batchmenu-bottom'); $anchorScroll(); $location.hash(old);
这样写是因为直接location.hash会导致url变化,页面跳转,所以加了防止跳转的代码.
6 使用路由切换不同模块需要全显示
- 在使用路由开发时,切换路由将会重新remove上一个模块的div,重新添加下个模板。一般项目中可能都会有需求需要固定头部header和sidebar,这样不同模板之间切换每次变化的都是ui-route(原生ngRoute)内的ui-view(ng-view)的template。如果有一个页面需要浏览器显示整个完整的页面,不包括头部和侧边栏。
- 可以使用ng-if绑定变量控制头部和侧边栏的显示和隐藏。
1.可以直接在service中做一个全局变量进行控制
2.消息广播方式:具体变量的绑定可以在controller中通过
7 ng-repeat的使用
项目中有个需求需要实时跟新列表数据list,按照传统的做法是使用定时器每隔10000ms向后台发送请求,和原来数据对比,数据跟新后首先移除本来的列表div,然后重新动态添加新数据列表,一般不使用任何框架的情况下,都是用jquery进行操作,但是这里有一个问题,就是实时数据量很大,就要不断的dom的操作,而dom操作是十分消耗性能的。
考虑到以上问题,采用Angular内部服务
ng-repeat在使用过程中还要考虑一个性能问题,这个坑也要十分注意,不然对整体性能的提升作用不大。
为了方便说明,这里举一个小小例子。
<body ng-app="myApp" ng-controller="myCtrl"> <button ng-click="request()">重新请求新数据</button> <ul> <li ng-repeat="data in records">{{data.name}}</li> </ul> </body> <script> var app = angular.module("myApp",[]); app.controller("myCtrl",function($scope) { var data1 = []; var data2 = []; for (var i = 0; i < 3; i++) { data2[i] = data1[i] = { id: i,name: "jay: " + i }; } for (var i = 0; i < 3; i++) { data2[i] = { id: "id"+i,name: "sum: " + i }; } $scope.records = data1; $scope.request = function () { // 模仿从服务器加载新数据 var result = data2; // 直接重新赋值给 records $scope.records = result; }; }); </script>
开始展示data1数组列表,点击请求数据后重新向$scope.records中替换数据data2,data1和data2长度相同但是内容不同。重新请求数据,查看ng-repeat中源码可以发现,当ng-repeat中的数组内容发生变化时,会将原来的dom删除,根据新的数据重新生成新的dom元素。
// remove existing items for (key in lastBlockMap) { // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn if (lastBlockMap.hasOwnProperty(key)) { block = lastBlockMap[key]; elementsToRemove = getBlockElements(block.clone); $animate.leave(elementsToRemove); forEach(elementsToRemove,function(element) { element[NG_REMOVED] = true; }); block.scope.$destroy(); } }
在代码中进行断点进入操作,发现即使使用了ng-repeat指令,还是对dom进行了频繁的消除和重建操作,对性能的提升并没有多大作用,我们不会想为什么ng-repeat不能利用已经存在的dom元素去跟新新加载的数据,而不是频繁的remove和create。
查看ng-repeat的使用API后发现,ng-repeat内有一个track by $index代码。
<li ng-repeat="data in records track by $index">{{data.name}}</li>
加上这段代码之后,重新刷新回到我们的例子,发现点击请求加载数据后,没有进入上那段代码之中了。这是为何呢?
删除track by $index
代码,重新操作添加前demo,可以看到ng-repeat往数组里的每个新添加的元素加了一个$$hashkey属性,这个key是由Angualr内部的nextUid()方法生成,类似数据库的自增id号,使用字符串进行唯一标示。这下明白,为什么dom不能重用,因为每次替换数组都会导致ng-repeat为每个元素生成一个新的key进行唯一标识,这样就没有办法重用已有的dom元素。
当使用ng-repeat时要尽量避免对全局列表的刷新。ng-repeat会产生一个$$hashkey属性和一系统唯一的项。这意味着当你调用 scope.listBoundToNgRepeat = serverFetch() 时会引起对整个列表的重新刷新。会通知执行所有的watchers并触发每一个元素,这是非常消耗性能的。这里有两种解决方案。一种是维护两个集合,和带有过虑器(filter)的ng-repeat(基本上需要自定义同步逻辑,因此算法更复杂,可维护性更差),另一种方案是使用track by去指定你自己的key(Angular 1.2 开始支持,只需要很少的同步逻辑)。
添加track by $index
后,重新查看,发现新添加的数组中没有出现$$hashkey属性,这样ng-repeat就可以将其缓存起来,仅仅对dom中的数据进行替换操作,从而提升性能。
同时我们查看dom树的跟新状态也能看出是重新创建了dom,还是只是对数据进行了替换。