AngularJS ng-repeat 很慢
AngularJS ng-repeat is slow
并不是说渲染很多条目很慢。问题是每当 $scope.data
更新时,它首先在元素末尾添加新项目,然后在匹配新的 $scope.data
.
时减少它
例如:
<div class="list" ng-repeat="entry in data">
<h3>{{entry.title}}</h3>
</div>
此脚本正在更新 $scope.data
:
$scope.load = function() {
$scope.data = getDataFromDB();
}
假设我在 $scope.data
中有 5 个条目。条目是:
[
{
id: 1,
title: 1
},
{
id: 2,
title: 2
},
......
]
当 $scope.data
已经有那些条目然后被重新加载($scope.data = getDataFromDB();
被调用)时,DOM 元素大约 0.1s - 0.2s 有 10 个元素(重复元素) , 然后在 0.1s - 0.2s 后减少到 5.
所以问题是更新ng-repeatDOM时有大约0.1s - 0.2s的延迟。当我实施实时搜索时,这看起来真的很糟糕。每当它从数据库更新时,ng-repeat DOM 元素每次都会加起来一小段时间。
如何使渲染变得即时?
已编辑
我会在这里粘贴我的所有代码:
控制器:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = [];
$scope.loadViewModels($scope.orderBy, table);
}
$scope.loadViewModels = function (orderBy, table, cb) {
if (!$scope.endOfPage) {
let searchKey = $scope.page.searchString;
let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
let searchClause = '';
if (searchKey && searchKey.length > 0) {
let searchArr = [];
$($scope.vmKeys).each((i, key) => {
searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
});
searchClause = `WHERE ` + searchArr.join(' OR ');
}
let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
sqlite.query(sc, rows => {
$scope.$apply(function () {
var data = [];
let loadedCount = 0;
if (rows != null) {
$scope.currentPage += 1;
loadedCount = rows.length;
if (rows.length < $scope.itemsPerPage)
$scope.endOfPage = true
for (var i = 0; i < rows.length; i++) {
let item = rows.item(i);
let returnObject = {};
$($scope.vmKeys).each((i, key) => {
returnObject[key] = item[key];
});
data.push(returnObject);
}
$scope.viewModels = $scope.viewModels.concat(data);
}
else
$scope.endOfPage = true;
if (cb)
cb(loadedCount);
})
});
}
}
观点:
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
<div class="row note-list" ng-if="showList">
<h3>Notes</h3>
<input ng-model="page.searchString" id="search"
ng-keyup="search('notes')" type="text" class="form-control"
placeholder="Search Notes" style="margin-bottom:10px">
<div class="col-12 note-list-item"
ng-repeat="data in viewModels track by data.id"
ng-click="edit(data.id)"
ontouchstart="touchStart()" ontouchend="touchEnd()"
ontouchmove="touchMove()">
<p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
ng-bind-html="data.title"></p>
<p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
<div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
<span class="btn fa fa-trash"></span>
</div>
</div>
<div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
<span class="fa fa-plus"></span>
</div>
</div>
<div ng-if="!showList" class="ease-in">
<div>
<div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
<div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
<span class="fa fa-check"></span>
</div>
</div>
<div contenteditable="true" class="note-title"
ng-bind-html="selected.title" id="title">
</div>
<div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
</div>
</div>
<script src="../js/pages/note.js"></script>
从以下位置调用它:
$scope.loadViewModels($scope.orderBy, 'notes');
sqlite 查询:
query: function (query, cb) {
db.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
return cb(res.rows, null);
});
}, function (error) {
return cb(null, error.message);
}, function () {
//console.log('query ok');
});
},
它是 apache cordova 框架,所以它在 Android 模拟器中使用 webview。
我的代码结构
<html ng-app="app" ng-controller="pageController">
<head>....</head>
<body>
....
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
....
</div>
</body>
</html>
所以控制器里面有控制器。 parent 是 pageController
而 child 是 noteController
。像这样的结构会减慢 ng-repeat
指令的速度吗?
顺便说一句,使用 track by
没有帮助。渲染时仍然有延迟。我也可以修改条目,所以当条目更新时,它也应该在列表中更新。
注意
仔细调查后发现了一些奇怪的事情。通常 ng-repeat 项目中有 hash
键。在我的情况下 ng-repeat 项目没有它。这是问题的原因吗?
在您的 html 中,试试这个:
<div class="list" ng-repeat="entry in data">
<h3 ng-bind="entry.title"></h3>
</div>
提高性能的一种方法是在 ng-repeat
表达式中使用 track by
子句:
<div class="list" ng-repeat="entry in data track by entry.id">
<h3>{{entry.title}}</h3>
</div>
来自文档:
Best Practice: If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance, e.g. item in items track by item.id
. Should you reload your data later, ngRepeat
will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
有关详细信息,请参阅
经过深入研究,我发现了我的问题。每次我重置/重新加载我的 $scope.viewModels
时,我总是先将它分配给 null / empty array
。这是导致渲染延迟的原因。
示例:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = []; <------ THIS
$scope.loadViewModels($scope.orderBy, table);
}
所以我没有将它分配给空/空数组,而是用新加载的数据替换它,闪烁消失了。
并不是说渲染很多条目很慢。问题是每当 $scope.data
更新时,它首先在元素末尾添加新项目,然后在匹配新的 $scope.data
.
例如:
<div class="list" ng-repeat="entry in data">
<h3>{{entry.title}}</h3>
</div>
此脚本正在更新 $scope.data
:
$scope.load = function() {
$scope.data = getDataFromDB();
}
假设我在 $scope.data
中有 5 个条目。条目是:
[
{
id: 1,
title: 1
},
{
id: 2,
title: 2
},
......
]
当 $scope.data
已经有那些条目然后被重新加载($scope.data = getDataFromDB();
被调用)时,DOM 元素大约 0.1s - 0.2s 有 10 个元素(重复元素) , 然后在 0.1s - 0.2s 后减少到 5.
所以问题是更新ng-repeatDOM时有大约0.1s - 0.2s的延迟。当我实施实时搜索时,这看起来真的很糟糕。每当它从数据库更新时,ng-repeat DOM 元素每次都会加起来一小段时间。
如何使渲染变得即时?
已编辑
我会在这里粘贴我的所有代码:
控制器:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = [];
$scope.loadViewModels($scope.orderBy, table);
}
$scope.loadViewModels = function (orderBy, table, cb) {
if (!$scope.endOfPage) {
let searchKey = $scope.page.searchString;
let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
let searchClause = '';
if (searchKey && searchKey.length > 0) {
let searchArr = [];
$($scope.vmKeys).each((i, key) => {
searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
});
searchClause = `WHERE ` + searchArr.join(' OR ');
}
let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
sqlite.query(sc, rows => {
$scope.$apply(function () {
var data = [];
let loadedCount = 0;
if (rows != null) {
$scope.currentPage += 1;
loadedCount = rows.length;
if (rows.length < $scope.itemsPerPage)
$scope.endOfPage = true
for (var i = 0; i < rows.length; i++) {
let item = rows.item(i);
let returnObject = {};
$($scope.vmKeys).each((i, key) => {
returnObject[key] = item[key];
});
data.push(returnObject);
}
$scope.viewModels = $scope.viewModels.concat(data);
}
else
$scope.endOfPage = true;
if (cb)
cb(loadedCount);
})
});
}
}
观点:
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
<div class="row note-list" ng-if="showList">
<h3>Notes</h3>
<input ng-model="page.searchString" id="search"
ng-keyup="search('notes')" type="text" class="form-control"
placeholder="Search Notes" style="margin-bottom:10px">
<div class="col-12 note-list-item"
ng-repeat="data in viewModels track by data.id"
ng-click="edit(data.id)"
ontouchstart="touchStart()" ontouchend="touchEnd()"
ontouchmove="touchMove()">
<p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
ng-bind-html="data.title"></p>
<p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
<div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
<span class="btn fa fa-trash"></span>
</div>
</div>
<div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
<span class="fa fa-plus"></span>
</div>
</div>
<div ng-if="!showList" class="ease-in">
<div>
<div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
<div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
<span class="fa fa-check"></span>
</div>
</div>
<div contenteditable="true" class="note-title"
ng-bind-html="selected.title" id="title">
</div>
<div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
</div>
</div>
<script src="../js/pages/note.js"></script>
从以下位置调用它:
$scope.loadViewModels($scope.orderBy, 'notes');
sqlite 查询:
query: function (query, cb) {
db.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
return cb(res.rows, null);
});
}, function (error) {
return cb(null, error.message);
}, function () {
//console.log('query ok');
});
},
它是 apache cordova 框架,所以它在 Android 模拟器中使用 webview。
我的代码结构
<html ng-app="app" ng-controller="pageController">
<head>....</head>
<body>
....
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
....
</div>
</body>
</html>
所以控制器里面有控制器。 parent 是 pageController
而 child 是 noteController
。像这样的结构会减慢 ng-repeat
指令的速度吗?
顺便说一句,使用 track by
没有帮助。渲染时仍然有延迟。我也可以修改条目,所以当条目更新时,它也应该在列表中更新。
注意
仔细调查后发现了一些奇怪的事情。通常 ng-repeat 项目中有 hash
键。在我的情况下 ng-repeat 项目没有它。这是问题的原因吗?
在您的 html 中,试试这个:
<div class="list" ng-repeat="entry in data">
<h3 ng-bind="entry.title"></h3>
</div>
提高性能的一种方法是在 ng-repeat
表达式中使用 track by
子句:
<div class="list" ng-repeat="entry in data track by entry.id">
<h3>{{entry.title}}</h3>
</div>
来自文档:
Best Practice: If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance, e.g.
item in items track by item.id
. Should you reload your data later,ngRepeat
will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
有关详细信息,请参阅
经过深入研究,我发现了我的问题。每次我重置/重新加载我的 $scope.viewModels
时,我总是先将它分配给 null / empty array
。这是导致渲染延迟的原因。
示例:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = []; <------ THIS
$scope.loadViewModels($scope.orderBy, table);
}
所以我没有将它分配给空/空数组,而是用新加载的数据替换它,闪烁消失了。