2

I have decent large data set of around 1100 records. This data set is mapped to an observable array which is then bound to a view. Since these records are updated frequently, the observable array is updated every time using the ko.mapping.fromJS helper.

This particular command takes around 40s to process all the rows. The user interface just locks for that period of time.

Here is the code -

var transactionList = ko.mapping.fromJS([]);

//Getting the latest transactions which are around 1100 in number;
var data = storage.transactions();

//Mapping the data to the observable array, which takes around 40s
ko.mapping.fromJS(data,transactionList)

Is there a workaround for this? Or should I just opt of web workers to improve performances?

4

4 回答 4

1

Knockout.viewmodel是 knockout.mapping 的替代品,它在为像这样的大型对象数组创建视图模型时要快得多。您应该注意到性能显着提高。

http://coderenaissance.github.com/knockout.viewmodel/

于 2012-12-26T22:28:18.997 回答
0

映射不是魔术。在大多数情况下,这个简单的递归函数就足够了:

function MyMapJS(a_what, a_path)
{
    a_path = a_path || [];

    if (a_what != null && a_what.constructor == Object)
    {
        var result = {};
        for (var key in a_what)
           result[key] = MyMapJS(a_what[key], a_path.concat(key));
        return result;
    }

    if (a_what != null && a_what.constructor == Array)
    {
        var result = ko.observableArray();
        for (var index in a_what)
           result.push(MyMapJS(a_what[index], a_path.concat(index)));
        return result;
    }

    // Write your condition here:
    switch (a_path[a_path.length-1])
    {
        case 'mapThisProperty':
        case 'andAlsoThisOne':
            result = ko.observable(a_what);
            break;
        default:
            result = a_what;
            break;
    }
    return result;
}

上面的代码从对象层次结构的任何级别的mapThisPropertyandAlsoThisOne属性中生成 observable;其他属性保持不变。您可以使用a_path.length来表示值所在的级别(深度),或者使用a_path的更多元素来表达更复杂的条件。例如:

if (a_path.length >= 2 
  && a_path[a_path.length-1] == 'mapThisProperty' 
  && a_path[a_path.length-2] == 'insideThisProperty')
    result = ko.observable(a_what);

您可以在条件中使用typeOf a_what,例如使所有字符串可观察。您可以忽略某些属性,并在某些级别插入新属性。或者,您甚至可以省略a_path。等等。

优点是:

  • 可定制(比knockout.mapping更容易)。
  • 足够短,可以复制粘贴它并在需要时为不同的对象编写单独的映射。
  • 较小的代码,knockout.mapping-latest.js不包含在您的页面中。
  • 应该更快,因为它只做绝对必要的事情。
于 2015-01-15T10:40:58.790 回答
0

我对映射插件有同样的问题。Knockout 团队表示,映射插件不适用于大型阵列。如果您必须将如此大的数据加载到页面,那么您的系统设计可能不正确。

解决此问题的最佳方法是使用服务器分页,而不是在页面加载时加载所有数据。如果您不想更改应用程序的设计,有一些解决方法可能对您有所帮助:

  1. 手动映射您的数组:

    var data = storage.transactions();
    var mappedData = ko.utils.arrayMap(data , function(item){
        return ko.mapping.fromJS(item);
    });
    
    var transactionList = ko.observableArray(mappedData);
    
  2. 异步映射数组。我编写了一个函数,在另一个线程中按部分处理数组并向用户报告进度:

    function processArrayAsync(array, itemFunc, afterStepFunc, finishFunc) {
        var itemsPerStep = 20;
    
        var processor = new function () {
            var self = this;
            self.array = array;
            self.processedCount = 0;
            self.itemFunc = itemFunc;
            self.afterStepFunc = afterStepFunc;
            self.finishFunc = finishFunc;
            self.step = function () {
                var tillCount = Math.min(self.processedCount + itemsPerStep, self.array.length);
                for (; self.processedCount < tillCount; self.processedCount++) {
                    self.itemFunc(self.array[self.processedCount], self.processedCount);
                }
    
                self.afterStepFunc(self.processedCount);
                if (self.processedCount < self.array.length - 1)
                    setTimeout(self.step, 1);
                else
                    self.finishFunc();
            };
        };
    
        processor.step();
    };
    

你的代码:

var data = storage.transactions();
var transactionList = ko.observableArray([]);

processArrayAsync(data,
    function (item) { // Step function
        var transaction = ko.mapping.fromJS(item);
        transactionList().push(transaction);
    },
    function (processedCount) { 
        var percent = Math.ceil(processedCount * 100 / data.length);
        // Show progress to the user.
        ShowMessage(percent);
    },
    function () { // Final function
        // This function will fire when all data are mapped. Do some work (i.e. Apply bindings).
    });

您也可以尝试替代映射库:knockout.wrap。它应该比映射插件更快。

我选择了第二个选项。

于 2012-09-07T09:14:40.883 回答
0

我还想到了一种解决方法,如下所示,这使用更少的代码-

var transactionList = ko.mapping.fromJS([]);

//Getting the latest transactions which are around 1100 in number;
var data = storage.transactions();

//Mapping the data to the observable array, which takes around 40s
// Instead of - ko.mapping.fromJS(data,transactionList)
var i = 0;

//clear the list completely first
transactionList.destroyAll();

//Set an interval of 0 and keep pushing the content to the list one by one.
var interval = setInterval(function () {if (i == data.length - 1 ) {
                                        clearInterval(interval);}

                                    transactionList.push(ko.mapping.fromJS(data[i++]));
                                }, 0);
于 2012-09-07T09:56:29.740 回答