【问题标题】:Knockout Performance - removeAll Observable Array淘汰赛性能 - removeAll Observable Array
【发布时间】:2017-07-05 06:47:43
【问题描述】:

在我的 html 视图中,我绑定了一个带有淘汰视图模型的 html 表,其中包含 3 个 observableArrays,如下所示:

function myViewModel() {
    var self = this;
    self.RowIds = ko.observableArray();
    self.ColIds = ko.observableArray();

    self.allCellsList = ko.observableArray();

    self.calculateText = function (r_id, c_id) {
        console.log('calculateText');
        var item = ko.utils.arrayFirst(self.allCellsList(), function (i) {
            return i.rowId() == r_id && i.colId() == c_id;
        });
        return item.text();
    }

    self.loadData = function (rowIds, colIds, allCellsList) {

        //remove old data (not necessary, only for testing , to see how long it takes)
        self.allCellsList.removeAll();

        //populate new data:
        self.RowIds(ko.mapping.fromJS(rowIds));
        self.ColIds(ko.mapping.fromJS(colIds));
        self.allCellsList(ko.mapping.fromJS(allCellsList));
    }

}

RowIds 包含表中每一行的 id 列表,CoIds 包含表中每一列的 id 列表。 allCellsList 是保存对象列表的主表,其中每个对象都包含两个标识符:rowId 和 colId。 在 table 的每个单元格中调用函数 calculateText ,并通过这两个字段计算 allCellsList 中的匹配数据。 如下:

<table>
    <tbody data-bind="foreach:RowIds">
        <tr data-bind="foreach:$root.ColIds()">
            <td>
                <div data-bind="text:$root.calculateText($data , $parent)">
                </div>
            </td>
        </tr>
    </tbody>
</table>

当我在 loadData 函数中使用新数据初始化 allCellsList 时,通过删除旧数据并获取新数据(或通过直接获取新数据,不使用 removeAll 函数) - 调用 calculateText 函数,对于每个项目在旧列表中,以及对于新列表中的每个项目。 如果旧列表包含大量记录 -​​ 清除数组操作需要太长时间。 (有时 3-4 秒)

我的问题是如何在这种情况下或在其他情况下填充 allCellsList,以便重新加载操作更快? 当 allCellsList 被清除时,有没有办法避免调用函数 calculateText ?

谢谢。

【问题讨论】:

  • 大型表本身就是 Knockout 中的性能问题。请参阅knockout-table,了解在 Knockout 中呈现表格的更好表现方式。

标签: javascript html knockout.js


【解决方案1】:

在调用RowIds.removeAllColIds.removeAll 时,不应运行calculateText 函数。但是,它在调用self.allCellsList.removeAll 时运行,因为此数组存在依赖关系。

要只得到一个计算,请按以下顺序执行操作:

self.loadData = function (rowIds, colIds, allCellsList) {
    self.RowIds.removeAll();
    self.ColIds.removeAll();

    // You don't need this and I'd suggest you removing it
    // self.allCellsList.removeAll();

    // First load the data that `calculateText` depends on
    self.allCellsList(ko.mapping.fromJS(allCellsList));

    // Calculate for these only when added to DOM
    self.ColIds(ko.mapping.fromJS(colIds));
    self.RowIds(ko.mapping.fromJS(rowIds));

}

编辑,进一步说明我的观点: 每次当 DOM 中有数据时清除 vals,您都会重新计算每个单元格...如果您确保没有数据在 DOM 中,当您设置 vals 时,每个单元格只能获得 一个

var valSource = { 
  a: { "1": "A1", "2": "A2", "3": "A3" }, 
  b: { "1": "B1", "2": "B2", "3": "B3" }, 
  c: { "1": "C1", "2": "C2", "3": "C3" }
};
var colSource = [{ id: "a" }, { id: "b" }, { id: "c" }];
var rowSource = [{ id: "1"   }, { id: "2" }];

var cols = ko.observableArray([]);
var rows = ko.observableArray([]);
var vals = ko.observable({});
var i = 0;

var getCellValue = function(col, row) {  
  i = i + 1;
  var col = vals()[col.id] || {};
  return col[row.id];
};


var updateSlow = function() {
  // Clear
  vals({}); // Triggers a recalc for every cell
  rows([]); // Remove these freshly recalced cells...
  cols([]); // Does nothing
  
  // Set
  rows(rowSource.slice(0)); // Adds rows
  cols(colSource.slice(0)); // Adds cells and recalcs
  vals(Object.assign({}, valSource)); // Recalcs again
  
  console.log("Updates slow:", i);
  i = 0;
};

var updateFast = function() {
  // Clear
  rows([]);
  cols([]);
  
  // Set
  vals(Object.assign({}, valSource)); // No DOM, so no recalc
  rows(rowSource.slice(0)); // Add rows
  cols(colSource.slice(0)); // Add cells and calculate once

  console.log("Updates fast:", i);
  i = 0;
}

ko.applyBindings({ cols: cols, rows: rows, getCellValue: getCellValue, updateSlow: updateSlow});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<button data-bind="click: updateSlow">update slow</button>
<button data-bind="click: updateFast">update fast</button>

<table data-bind="foreach: rows">
  <tr data-bind="foreach: cols">
    <td data-bind="text: getCellValue($data, $parent)"></td>
  </tr>
</table>

【讨论】:

  • 正如我在问题中提到的,即使我直接在数组中设置新数据(如您的示例),也会为旧数据中的每个项目调用计算。所以它无助于提高重型性能
  • 我添加了一个示例以更好地说明我试图解释的内容。
  • 非常感谢您提供的详细示例。按照你提到的匹配我的代码后,我注意到重型性能没有完全修复,只有一点点。这意味着问题是由另一个原因引起的。也许重置与模型中的列表关联的 DOM 可以解决问题。这个问题阻碍了我们,所以如果您对如何避免繁重的性能有其他想法,我们将不胜感激。
  • calculateText 方法中的查找非常繁重(您循环遍历每个单元格的 i 项)。我建议在这个数组上循环一次以创建地图,然后为每个重复使用的单元创建可重复使用的视图模型,并具有计算的 text 属性。这使剔除可以缓存未更改的单元格,并且仅更新 DOM 以进行实际更改。不过,您必须向我展示您的一些数据才能提供帮助,因此可能值得提出一个新问题...
猜你喜欢
  • 2013-04-10
  • 2013-02-07
  • 1970-01-01
  • 2014-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-02
相关资源
最近更新 更多