【问题标题】:facing performance issues with knockout mapping plugin面临淘汰赛映射插件的性能问题
【发布时间】:2012-08-31 13:29:51
【问题描述】:

我有大约 1100 条记录的大型数据集。这个数据集被映射到一个可观察的数组,然后绑定到一个视图。由于这些记录经常更新,因此每次使用 ko.mapping.fromJS 帮助器都会更新 observable 数组。

这个特殊的命令需要大约 40 秒来处理所有的行。用户界面只会锁定这段时间。

这里是代码 -

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)

有解决办法吗?还是我应该选择网络工作者来提高性能?

【问题讨论】:

    标签: performance knockout.js knockout-mapping-plugin


    【解决方案1】:

    我在使用映射插件时遇到了同样的问题。 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。它应该比映射插件更快。

    我选择了第二个选项。

    【讨论】:

    • 非常有趣的解决方案。创建一个 JsPerf 来看看它的速度有多快会很有趣。
    • @billy 编辑帖子时请小心。更改原始问题的编码样式被认为与更改帖子的含义相同。我回滚了编辑。
    • 这里是 jsperf:jsperf.com/…(我是正在测试的自定义映射器的作者)
    【解决方案2】:

    我也想到了一个解决方法如下,这样使用的代码量更少-

    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);
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 我需要一些时间来看看它是否真的能提高性能。您能否提供一个示例代码来描述我如何在我的情况下特别使用它?
      【解决方案4】:

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

      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;
      }
      

      上面的代码从对象层次结构的任何级别的 ma​​pThisPropertyandAlsoThisOne 属性中生成 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 未包含在您的页面中。
      • 应该更快,因为它只执行绝对必要的操作。

      【讨论】:

        猜你喜欢
        • 2012-10-03
        • 2013-05-27
        • 2015-03-30
        • 2016-11-06
        • 2013-07-16
        • 1970-01-01
        • 2012-09-05
        • 1970-01-01
        • 2012-06-08
        相关资源
        最近更新 更多