在半大数据集下Knockout.js非常慢

Mik*_*sen 86 javascript performance knockout.js

我刚刚开始使用Knockout.js(一直想尝试一下,但现在我终于有了借口!) - 然而,当我将一个表绑定到一个相对较小的一组时,我遇到了一些非常糟糕的性能问题数据(约400行左右).

在我的模型中,我有以下代码:

this.projects = ko.observableArray( [] ); //Bind to empty array at startup

this.loadData = function (data) //Called when AJAX method returns
{
   for(var i = 0; i < data.length; i++)
   {
      this.projects.push(new ResultRow(data[i])); //<-- Bottleneck!
   }
};
Run Code Online (Sandbox Code Playgroud)

问题是for上面的循环大约需要30秒左右,大约需要400行.但是,如果我将代码更改为:

this.loadData = function (data)
{
   var testArray = []; //<-- Plain ol' Javascript array
   for(var i = 0; i < data.length; i++)
   {
      testArray.push(new ResultRow(data[i]));
   }
};
Run Code Online (Sandbox Code Playgroud)

然后for循环在眨眼间完成.换句话说,pushKnockout observableArray对象的方法非常慢.

这是我的模板:

<tbody data-bind="foreach: projects">
    <tr>
       <td data-bind="text: code"></td>
       <td><a data-bind="projlink: key, text: projname"></td>
       <td data-bind="text: request"></td>
       <td data-bind="text: stage"></td>
       <td data-bind="text: type"></td>
       <td data-bind="text: launch"></td>
       <td><a data-bind="mailto: ownerEmail, text: owner"></a></td>
    </tr>
</tbody>
Run Code Online (Sandbox Code Playgroud)

我的问题:

  1. 这是将我的数据(来自AJAX方法)绑定到可观察集合的正确方法吗?
  2. 我希望push每次调用它时都会进行一些重度重新计算,例如重建绑定的DOM对象.有没有办法延迟这个rec​​alc,或者可能一次推入我的所有项目?

如果需要,我可以添加更多代码,但我很确定这是相关的.在大多数情况下,我只是关注网站上的Knockout教程.

更新:

根据以下建议,我更新了我的代码:

this.loadData = function (data)
{
   var mappedData = $.map(data, function (item) { return new ResultRow(item) });
   this.projects(mappedData);
};
Run Code Online (Sandbox Code Playgroud)

但是,this.projects()400行仍需要大约10秒钟.我承认我不确定没有 Knockout 会有多快(只是通过DOM添加行),但我觉得它会比10秒快得多.

更新2:

根据下面的其他建议,我给了jQuery.tmpl一个镜头(KnockOut原生支持),这个模板引擎将在3秒内绘制大约400行.这似乎是最好的方法,缺少在滚动时动态加载更多数据的解决方案.

Jim*_* G. 50

请参阅:Knockout.js Performance Gotcha#2 - 操作observableArrays

更好的模式是获取对底层数组的引用,推送到它,然后调用.valueHasMutated().现在,我们的订阅者只会收到一个通知,表明该阵列已更改.


mad*_*kay 16

正如评论中所建议的那样.

Knockout拥有与(foreach,with)绑定相关联的自己的本机模板引擎.它还支持其他模板引擎,即jquery.tmpl.请阅读此处了解更多详情.我没有对不同的引擎进行任何基准测试,所以不知道它是否会有所帮助.阅读您之前的评论,在IE7中,您可能很难获得所追求的性能.

顺便说一句,KO支持任何js模板引擎,如果有人为它编写了适配器.您可能想尝试其他人,因为jquery tmpl将被JsRender取代.

  • 招魂![jquery.tmpl](https://github.com/BorisMoore/jquery-tmpl)已不再开发中 (4认同)

Tim*_*ord 13

除了使用$ .map之外,还要使用KO 分页.

我使用1400个记录的大型数据集时遇到了同样的问题,直到我使用了敲除分页.使用$.map加载记录确实产生了巨大的差异,但DOM渲染时间仍然很可怕.然后我尝试使用分页,这使我的数据集照明快速 - 以及更加用户友好.页面大小为50使得数据集的压倒性更大,并且显着减少了DOM元素的数量.

它很容易用KO做:

http://jsfiddle.net/rniemeyer/5Xr2X/


del*_*ree 11

KnockoutJS有一些很棒的教程,特别是有关加载和保存数据的教程

在他们的情况下,他们使用getJSON()非常快的数据来提取数据.从他们的例子:

function TaskListViewModel() {
    // ... leave the existing code unchanged ...

    // Load initial state from server, convert it to Task instances, then populate self.tasks
    $.getJSON("/tasks", function(allData) {
        var mappedTasks = $.map(allData, function(item) { return new Task(item) });
        self.tasks(mappedTasks);
    });    
}
Run Code Online (Sandbox Code Playgroud)


eri*_*icb 9

KoGrid看一看.它可以智能地管理您的行渲染,从而提高性能.

如果您尝试使用绑定将400行绑定到表中foreach,那么您将无法通过KO将其推入DOM中.

KO使用foreach绑定做了一些非常有趣的事情,其中大部分是非常好的操作,但随着数组大小的增长,它们确实开始打破perf.

我一直在试图将大型数据集绑定到表/网格上的漫长黑暗道路,最终需要在本地拆分/分页数据.

KoGrid做到了这一切.它被构建为仅渲染查看者可以在页面上看到的行,然后虚拟化其他行直到需要它们.我想你会发现400件物品的性能比你遇到的要好得多.


teh*_*aus 5

避免在渲染非常大的数组时锁定浏览器的解决方案是“限制”数组,以便一次仅添加几个元素,并且在两者之间进行睡眠。这是一个将执行此操作的函数:

function throttledArray(getData) {
    var showingDataO = ko.observableArray(),
        showingData = [],
        sourceData = [];
    ko.computed(function () {
        var data = getData();
        if ( Math.abs(sourceData.length - data.length) / sourceData.length > 0.5 ) {
            showingData = [];
            sourceData = data;
            (function load() {
                if ( data == sourceData && showingData.length != data.length ) {
                    showingData = showingData.concat( data.slice(showingData.length, showingData.length + 20) );
                    showingDataO(showingData);
                    setTimeout(load, 500);
                }
            })();
        } else {
            showingDataO(showingData = sourceData = data);
        }
    });
    return showingDataO;
}
Run Code Online (Sandbox Code Playgroud)

根据您的用例,这可能会导致UX的大幅改进,因为用户可能只需要在滚动之前看到第一批行。


mit*_*aka 5

在我的案例中,利用push()接受变量参数的性能最佳。1300行加载了5973毫秒(〜6秒)。通过这种优化,加载时间减少到914ms(<1秒)。
改进了84.7%!

有关将项目推送到observableArray的更多信息

this.projects = ko.observableArray( [] ); //Bind to empty array at startup

this.loadData = function (data) //Called when AJAX method returns
{
   var arrMappedData = ko.utils.arrayMap(data, function (item) {
       return new ResultRow(item);
   });
   //take advantage of push accepting variable arguments
   this.projects.push.apply(this.projects, arrMappedData);
};
Run Code Online (Sandbox Code Playgroud)