KnockoutJS foreach阻止主线程

时间:2011-11-16 19:42:25

标签: javascript knockout.js

当我在viewModel中有一个大型数据集时,我使用foreach循环一个对象数组来将每个Object渲染为一个表中的一行,KnockoutJS将阻塞主线程直到它可以渲染,有时需要几分钟(!)。

这是一个使用包含2000个对象的数据集的jsFiddle示例,其中包含urlcode。在某些情况下,真实数据会有更长的URL和其他4列(本例中只有2个。)我还添加了一些简单的样式,因为在此过程中添加样式似乎也会使事情变慢。

  

警告:您的浏览器可能中断

http://jsfiddle.net/DESC3/7/

2 个答案:

答案 0 :(得分:1)

我建议如果你有这么大的数据集,你可以尝试另一种解决方案。例如,slickGrid通过仅为实际可见的数据生成HTML元素,以更有效的方式呈现大型数据集。我们已经将它用于大型数据集,并且表现良好。

答案 1 :(得分:1)

这样的事情怎么样?说,你有viewModel.items = ko.observableArray()你想要渲染。

  1. 为您的所有数据设置单独的不可观察数组:var itemsToRender = functionThatReturnsLargeArray()
  2. itemsToRender中的部分数据放入可观察数组中。比如,只有50个元素。
  3. 继续在setTimeout回调中的部分中将元素添加到可观察数组中。

  4. 注意1:您可以在setTimeout回调中添加一些时间跟踪,并增加/减少每次迭代时添加的项目数。您的目标是将每个回调时间保持在50-100毫秒以下,以便您的应用程序仍能感受到响应。

    var batchSize = 50; // default number of items rendered per iteration
    var batchOffset = 0;
    function render(items, itemsToRender, done) {
        setTimeout(function () {
            var startTime = new Date().getTime();
            items.pushAll(itemsToRender.slice(batchOffset, batchSize));
            batchOffset += batchSize;
            // at this point Knockout rendered next batchSize items from itemsToRender
            var endTime = new Date().getTime();
            // update batchSize for next iteration
            batchSize = batchSize * 50 / (endTime - startTime); // 50 milliseconds
            batchSize = Math.min(itemsToRender.length, batchOffset + batchSize);
            if (batchSize > 0) render() else done(); // callback if you need one
        }, 0);
    }
    /* I haven't actually tested the code */
    

    另一种批量更新策略可以基于目标FPS。假设您希望达到60 fps的更新速率,因此每1000毫秒可以调用setTimeout 60次。处理整个集合需要更长的时间。您也可以使用requestAnimationFrame代替setTimeout,看看它会如何解决。

    编辑Build-in throttling已添加到Knockout JS 1.3(目前处于测试阶段,但似乎相当稳定)。


    注2:如果视图上的其他一些数据取决于viewModel.items,您仍然可以将其映射到原始数组itemsToRender。比如说,您想要显示集合中的项目数。如果您使用viewModel.items().length,您最终会在UI中更改大小值,而更多项目会被渲染。为避免这种情况,您可以先根据dependentObservable将大小绑定定义为itemsToRender,而不是viewModel.items。完成所有项目的渲染后,如果您认为合适,可以将其重新映射到viewModel.items