【发布时间】:2015-10-17 20:28:15
【问题描述】:
我有一个 1000 多个项目的列表,我在 Angular 1.3 中使用 NgRepeat 显示这些项目。该列表填充有按钮。我注意到列表中的点击事件一旦变大,就会出现明显的延迟。当列表只有 5-10 个项目时,点击是即时的。当列表为 1000 时,在实际处理按钮点击之前大约有 2-5 秒的延迟。
现在我无法判断这是否是浏览器问题,但我怀疑这与某处使用的侦听器过多有关,导致浏览器检查它们。
这里是代码示例,以防有罪魁祸首隐藏在那里:
<div id="side" class="animated" style="min-height: 250px;"
data-ng-class="{'fadeInRight':documentDone}" data-ng-style="settings.listCss">
<div class="col-md-12 text-center" data-ng-style="settings.listCss"><h4>{{label}}</h4> {{inSide}} </div>
<div data-ng-repeat="doc in ::documents track by $index" id="{{ ::doc.id }}"
class="document ng-hide" data-ng-show="doc.show"
data-ng-init="docSettings = (settingslist[doc.companyid] || settings)" data-ng-style="::docSettings.listCss">
<div class="col-md-12" data-ng-style="docSettings.listCss">
<h4>
<span>{{ ::$index + 1 }}</span>
<span class="title-in-clusters">
{{ ::doc.title }}
<button type="button"
class="btn btn-primary btn-xs"
data-ng-click="viewJob(doc, docSettings)"
data-ng-style="docSettings.buttonCss">
<strong>VIEW</strong>
</button>
<a href="{{ ::doc.joburl }}" class="apply" target="_blank">
<button type="button" class="btn btn-primary btn-xs" data-ng-click="apply(doc.jobid, doc.companyid)"
data-ng-style="docSettings.buttonCss">
<strong>APPLY</strong>
</button>
</a>
</span>
</h4>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<span class=""><strong>ID: </strong>{{ ::doc.jobid }}</span>
<img data-ng-if="docSettings.heading.logourl && docSettings.heading.logourl != ''"
data-ng-src="{{docSettings.heading.logourl}}" class="side-logo inline-block" id="">
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<strong>Location: </strong><span class="">{{ ::doc.location }}</span>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<strong>Updated Date: </strong><span class="">{{ ::doc.updateddate }}</span>
</div>
<div class="col-md-12" data-ng-style="docSettings.listCss">
<hr data-ng-style="docSettings.listCss">
</div>
</div>
</div>
按下按钮时调用的其他函数没有任何冒犯性。
var modalInstance;
$scope.viewJob = function(modalDoc, docSettings) {
$scope.modalDoc = modalDoc;
$scope.docSettings = docSettings;
//the trusAsHtml takes string creates an object, so this will in essence convert string to object
//make sure you check if it is a string since it could be called multiple times by user (close and reopen same modal)
if (modalDoc.overview && typeof modalDoc.overview === 'string') {
$scope.modalDoc.overview = $sce.trustAsHtml(modalDoc.overview);
}
if (modalDoc.qualifications && typeof modalDoc.qualifications === 'string') {
$scope.modalDoc.qualifications = $sce.trustAsHtml(modalDoc.qualifications);
}
if (modalDoc.responsibilities && typeof modalDoc.responsibilities === 'string') {
$scope.modalDoc.responsibilities = $sce.trustAsHtml(modalDoc.responsibilities);
}
modalInstance = $modal.open({
templateUrl: 'app/modal/job_preview.html',
//templateUrl: 'myModalContent.html',
scope: $scope
});
};
我想优化此代码,使其可以处理多达 1500 个列表,但我终生无法找到罪魁祸首。
我也会采取任何解决方案来减少负载。就像现在我在想我可能会将 DOM 元素的数量限制为 10 个,并且如果它会导致更好的用户体验,那么可以将被视为用户滚动的内容进行角度旋转。
更新:
已经尝试了很多事情,从使用一次绑定到更复杂的解决方案,retard some of the watchers 是 enat 但需要大量数学来估计哪些项目是可见的等等。
我最终决定了一个最容易做到的解决方案:我列出了我希望显示的唯一项目,然后在鼠标上下滚动时编辑列表。
解决方案的第一部分是使用两个指令:
.directive('ngMouseWheelUp', function() {
return function($scope, $element, $attrs) {
$element.bind("DOMMouseScroll mousewheel onmousewheel",
function(event) {
// cross-browser wheel delta
var event = window.event || event; // old IE support
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
if(delta > 0) {
$scope.$apply(function(){
$scope.$eval($attrs.ngMouseWheelUp);
});
// for IE
event.returnValue = false;
// for Chrome and Firefox
if(event.preventDefault) {
event.preventDefault();
}
}
});
};
})
.directive('ngMouseWheelDown', function() {
return function($scope, $element, $attrs) {
$element.bind("DOMMouseScroll mousewheel onmousewheel", function(event) {
// cross-browser wheel delta
var event = window.event || event; // old IE support
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
//console.log(event);
if(delta < 0) {
$scope.$apply(function(){
$scope.$eval($attrs.ngMouseWheelDown);
});
// for IE
event.returnValue = false;
// for Chrome and Firefox
if(event.preventDefault) {
event.preventDefault();
}
}
});
};
})
这两个使我能够禁用右侧列表中的滚动。然后,我将从 routeScope 中的文档创建两个额外的数组。每当更新文档时都会生成第一个列表(这是右侧图表 UI 发出的事件的事件侦听器),此过滤器将仅返回 show 属性设置为 true 的数组成员:
var showFilter = function(object) {
return object.show;
}
这将是我的可见项目数组。从这个数组中,我创建了另一个显示项目的数组。我为最大尺寸 7 定义了一个常数,所以最多显示 7 个项目。当然,我将父容器的溢出设置为 none 以禁用滚动条。 (我可能会添加一个滚动图形,以便用户知道他可以稍后滚动该字段)
然后我在侧 div 中添加了以下指令: data-ng-mouse-wheel-up="listUp()" data-ng-mouse-wheel-down="listDown()" 在控制器内部,我定义了 listUp 和 listDown 来处理索引和最大大小常量,以确定我应该将可见列表中的哪些元素添加到显示列表的前面或后面。
/**
* Simulate scrolling up of list by removing bottom element and adding to top
*/
$scope.listUp = function() {
$rootScope.shownDocuments.unshift(getPrev());
$rootScope.shownDocuments.pop();
}
/**
* Simulate scrolling down of list by removing top element and adding to bottom
*/
$scope.listDown = function() {
$rootScope.shownDocuments.push(getNext());
$rootScope.shownDocuments.shift();
}
/**
* return next item in visibleDocuments array
*/
var getNext = function() {
$rootScope.topIndex++;
if ($rootScope.topIndex > $rootScope.visibleDocuments.length) {
$rootScope.topIndex -= $rootScope.visibleDocuments.length;
}
return ($rootScope.visibleDocuments[($rootScope.topIndex+max_shown_size)%$rootScope.visibleDocuments.length]);
}
/**
* Return previous item in visibleDocuments array
*/
var getPrev = function() {
$rootScope.topIndex--;
if ($rootScope.topIndex < 0) {
$rootScope.topIndex += $rootScope.visibleDocuments.length;
}
return ($rootScope.visibleDocuments[$scope.topIndex]);
}
使用 rootScope vs scope 主要是因为模态框如果被不当解除会导致一些不良行为。
最后是视图的重置功能:
/**
* Resets the list of documents in the visibleList (IE which are visible to client)
*/
var updateVisibleDocuments = function() {
$rootScope.topIndex = 0;
$rootScope.visibleDocuments = $rootScope.documents.filter(showFilter);
//clear view
$rootScope.shownDocuments = [];
$rootScope.topIndex = 0;
for (var i = 0; i < max_shown_size; i++) {
$rootScope.shownDocuments.push(getNext());
}
$rootScope.topIndex = 0;
}
这个解决方案非常有效,因为即使我的列表有 100k 个项目,我也只渲染 7 个项目。这极大地限制了观察者的数量。
【问题讨论】:
-
你有样本数据吗?或者可以制作jsfiddle或plunker?应该有一些方法可以改善这一点。
-
我正在研究 plunker,但我很难在那里复制问题。我已经附上了一个指向实际产品网站的链接。 job-portal.rightfit.it/#/company/comrise
-
如果你想看到性能下降,请访问此链接job-portal.rightfit.it/#/company/ADP 300 之后它变得非常明显。
-
有 18k $watchers,我不会对它的缓慢感到惊讶,而且看起来也像开发构建。在我的计算机中下载 js 文件需要 34 秒,而 Angular 需要 10 秒。我将在这个周末做出更快的替代方案,并将其发布。我将使用一些可以改善这一点的库。
-
谢谢。我有一个新的 chrome 扩展程序,它可以跟踪观察者的数量而不会崩溃,我也注意到了这一点。我试图在有时间的时候找到瓶颈。本来没想到会超过 200 个,所以已经足够好了,但项目走的是升级路线。
标签: angularjs button ng-repeat