【问题标题】:Angularjs - optimizing the watcher of ngrepeat to watch only certain properties of collectionAngularjs - 优化 ngrepeat 的观察者以仅观察集合的某些属性
【发布时间】:2014-04-07 14:57:13
【问题描述】:

在我的 angularJS 应用程序中,我有一个相当大的对象的集合(数组)。我需要在不同的地方绑定到这个集合(例如,显示属性:包含对象的名称) - 绑定是必不可少的,因为这些名称可能会改变。

由于正常的 ngRepeat 会通过严格的相等比较来观察整个集合,我担心应用程序的速度(我们正在谈论具有数千个或更多属性的对象) - 我实际上只需要观察集合中的一般变化(比如长度,在两个元素被翻转的情况下单个引用的变化以及一些特定的属性,如提到的 .name 属性)

我正在考虑使用以下方法(基本上是创建集合的自定义副本并手动绑定到原始集合。

我的问题: 所描述的方法是否比观看原始集合更好(通过相等性 - 正如我理解 ngRepeater 所做的那样)还是有更好的方法(例如,在 watch 语句中定义某种比较回调以仅检查某些属性的变化, ...)

<script>
function QuickTestController($scope) {
    // simulate data from a service
    var serviceCollection = [], counter = 0,
        generateElement = function() {
            var element = { name:'name' + ++counter };
            //var element = { name:'name' };
            for (var j = 0 ; j < 5 ; j++) element['property' + j] = j;

            return element;
        };

    for (var i = 0 ; i < 5 ; i++) {
        serviceCollection.push( generateElement() );
    }

    // in the view controller we could either bind to the service collection directly (which should internally use a watchCollection and watch every single element for equality)
    $scope.viewCollection = serviceCollection;

    // watching equality of collection
    /*
    $scope.$watch('_viewCollectionObserve', function(newValue, oldValue) {
        console.log('watch: ', newValue, oldValue);
    }, true);
    */

    // or we could create our own watchCollection / watch structure and watch only those properties we are interested in
    $scope._viewCollectionObserve = serviceCollection;

    var viewCollectionManual = [],
        rebuildViewCollection = function() {
            viewCollectionManual = [];
            for (var i = 0, length = serviceCollection.length ; i < length ; i++) {
                viewCollectionManual.push( {name:serviceCollection[i].name } );
            }
            console.log('- rebuildViewCollection - ');
            $scope.viewCollection2 = viewCollectionManual;
        },
        watchCollectionProperties = [],
        unregisterWatchCollection = function() {},
        rebuildWatchCollectionProperties = function() {
            watchCollectionProperties = [];
            for (var i = 0, length = serviceCollection.length ; i < length ; i++) {
                watchCollectionProperties.push('_viewCollectionObserve[' + i + ']'); // watch for ref changes
                watchCollectionProperties.push('_viewCollectionObserve[' + i + '].name'); // watch for changes in specific properties
            }
            unregisterWatchCollection();
            var watchString = '[' + watchCollectionProperties.join(',') + ']';
            unregisterWatchCollection = $scope.$watchCollection(watchString, function(newValues, oldValues) {
                console.log('watchCollection: ', newValues, oldValues);
                rebuildViewCollection();
            });
        };

        $scope.$watch('_viewCollectionObserve.length', function(newValue, oldValue) { // watch add / remove elements to / from collection
            console.log('watch / length: ', newValue, oldValue);
            rebuildWatchCollectionProperties();
        });


        // rebuildViewCollection();
        rebuildWatchCollectionProperties();


    // click handler ---
    $scope.changName = function() { serviceCollection[0].name += '1'; };
    $scope.changeSomeProperty = function() { serviceCollection[0].property0 += 1; };
    $scope.removeElement = function() { serviceCollection.splice(0, 1); };
    $scope.addElement = function() { serviceCollection.push( generateElement() ); };
    $scope.switchElement = function() {
        var temp = serviceCollection[0];
        serviceCollection[0] = serviceCollection[1];
        serviceCollection[1] = temp;
    };
    // will of course not react to this (this is desired behaviour!)
    $scope.removeCollection = function() {  serviceCollection = []; };
}
</script>

<div data-ng-controller="QuickTestController">
    <ul>
        <li data-ng-repeat="element in viewCollection">{{element.name}} {{element}}</li>
    </ul>
    <hr>
    <ul>
        <li data-ng-repeat="element in viewCollection2">{{element.name}} {{element}}</li>
    </ul>

    <button data-ng-click="changName()">changName</button>
    <button data-ng-click="changeSomeProperty()">changeSomeProperty</button>
    <button data-ng-click="removeElement()">removeElement</button>
    <button data-ng-click="addElement()">addElement</button>
    <button data-ng-click="switchElement()">switchElement</button>
    <hr>
    <button data-ng-click="removeCollection()">removeCollection (see comment)</button>  
</div>

任何帮助/意见将不胜感激 - 请注意我试图创建一个小提琴来展示我的方法但失败了:-(

(我知道基准测试可能是测试我的方法的一种可能的解决方案,但我宁愿在这里了解 angularjs 专业人士的意见)

谢谢, 马蒂亚斯

【问题讨论】:

    标签: performance angularjs angularjs-ng-repeat


    【解决方案1】:

    我认为您正在寻找的是bindonce,这是一个高性能绑定指令,可以让您在 AngularJS 中绑定一次属性或表达式,正如其名称所暗示的那样。

    【讨论】:

    • 感谢您的回复!但是,我知道您提到的指令,尽管 angularjs 团队似乎甚至打算在将来的版本中将这个功能包含在核心中,但 bindonce 在这里没有解决方案,因为它没有 ngRepeat 和删除的选项初始编译/构建阶段之后的实际绑定 - 这将阻止列表的任何进一步更新(如我的示例所示) - 问候,matthias
    【解决方案2】:

    您还可以尝试使用“track by”表达式。如果集合中的每个对象都有一个唯一的属性,则可以将其传递给重复表达式。

    <div ng-repeat="item in items track by item.id"></div>
    

    我认为 Angular 会在你的每个项目上只观察该属性。所以这应该会提高性能,但我不知道能提高多少。

    【讨论】:

    • 感谢您的回复-正如我所经历的那样,当您处理昂贵的 DOM 创建时,按表达式跟踪部分主要会派上用场-正如bennadel.com/blog/… 中所展示的那样-问候,matthias
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    • 2018-04-10
    • 1970-01-01
    • 2015-08-20
    • 2018-08-09
    • 1970-01-01
    相关资源
    最近更新 更多