AngularJs 的 ng-repeat 讓我們非常方便的遍歷數組生成 Dom 元素,但是使用不當也會有性能問題。
在項目中我們使用 ng-repeat 加載完一個列表后,如果再次請求數據,然后過濾列表,代碼可能會這么寫:
<div ng-controller="Test">
<button ng-click="request()">請求新數據</button>
<div ng-repeat="user in users">
{{user.name}}
</div>
</div>
Controller 的代碼:
app.controller('Test', function($scope) {
var users = [];
for (var i = 0; i < 100; i++) {
users[i] = {
id: i,
name: "User: " + i
};
}
$scope.users = users;
$scope.request = function () {
// 從服務器加載新數據
var result = [];
// 直接重新賦值給 users
$scope.users = result;
};
});
查看 ng-repeat 的源碼可以發現,當 ng-repeat 的數組被替換時, 它默認並不會重新利用已有的 Dom 元素,而是直接將其全部刪除並重新生成新的數組 Dom 元素:
// 將上次生成的所有 dom 移除
for (key in lastBlockMap) {
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();
}
}
Dom 的頻繁操作是非常不友好的,為什么 ng-repeat 不能利用已有的 dom 元素去更新數據呢?因為你沒有把數組元素的標識屬性告訴它,那么兩次替換的時候它就沒辦法追蹤了,我們可以看到 ng-repeat 往數組里每個元素加了一個 $$hashKey 的屬性:
這個 key 是由 Angular 內部的 nextUid() 方法生成,類似數據庫自增,但是是使用字符串。
現在我們明白了,因為每次替換數組都會導致 ng-repeat 為每個元素生成一個新 key, 所以根本沒辦法重用已有的 Dom 元素,那么我們可以使用下邊的語法來避免這個問題:
<div ng-controller="Test">
<button ng-click="request()">請求新數據</button>
// 使用 track by 標識
<div ng-repeat="user in users track by user.id">
{{user.name}}
</div>
</div>
這樣 ng-repeat 就用將其緩存起來啦,當然可能你的數組元素沒有一個標識屬性,如果元素數量不多那么可以接受,不然還是建議你手動為其生成一個標識屬性。