項目中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){
});
樣本demo 2 雙向資料繫結{{}}使用重新整理出現{{}}
由於後台資料問題沒有訪問成功,前台介面就不需要顯示{{}},用ng-bind指令代替{{}} 3 rootScrope以及和 rootScrope以及和scope的區別。 rootScrope頁面所有 rootScrope 頁面所有scope的父親。 如何產生 rootScope和 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中通過 scope. scope.emit向上發送一個訊息,然後頁面的controller通過 scope. scope.on監聽訊息,一旦收到訊息,改變變數值。 7 ng-repeat的使用
項目中有個需求需要即時跟新列表資料list,按照傳統的做法是使用定時器每隔10000ms向後台發送請求,和原來資料對比,資料跟新後首先移除本來的列表div,然後重新動態添加新資料列表,一般不使用任何架構的情況下,都是用jquery進行操作,但是這裡有一個問題,就是即時資料量很大,就要不斷的dom的操作,而dom操作是十分消耗效能的。
考慮到以上問題,採用Angular內部服務 http進行後台資料請求,同時在控制器中使用 http進行後台資料請求,同時在控制器中使用scope.$watch()監聽資料的變化,和舊資料對比是否發生變化,然後使用內部指令ng-repeat便利數組產生Dom元素,進行列表顯示。
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,還是只是對資料進行了替換。
8.使用雙向資料繫結搭建多級聯動操作
雙向資料繫結搭建多級聯動操作
9關於ui-router記憶體泄露問題的github上討論
路由記憶體泄露問題