【问题标题】:How can I repeatedly access a Vue prop without tanking performance?如何在不影响性能的情况下重复访问 Vue 道具?
【发布时间】:2021-04-10 16:15:16
【问题描述】:

我正在开发一个存在性能问题的 Vue/Vuetify 应用程序。我创建了一个环绕a standard Vuetify v-data-table component 的自定义组件。它适用于少量数据,但提供中到大量数据会导致 Firefox 挂起,Chrome 崩溃。

这是我的代码的稍微简化的版本:

<script>
import ...

export default {
  props: {
    theValues: Array,
    // other props
  },

  computed: {
    theKeys: function() {
      return this.schema.map(col => col.col);
    },

    schema: function() {
      return this.theValues[0].schema;
    },

    dataForDataTable: function() {
      console.time('test');

      let result = [];
      for (let i = 0; i < theValues[0].data.length; i++) {
        let resultObj = {};
        for (let j = 0; j < theKeys.length; j++) {
          // The "real" logic; this causes the browser to hang/crash
          // resultObj[theKeys[j]] = theValues[0].data[i][j];

          // Test operation to diagnose the problem
          resultObj[theKeys[j]] = Math.floor(Math.random() * Math.floor(99999));
        }
        result.push(resultObj);
      }

      console.timeEnd('test');
      // For ~30k rows, timer reports that:
      // Real values can take over 250,000 ms
      // Randomly generated fake values take only 7 ms

      return result;
    },

    // other computed
  },

  // other Vue stuff
</script>

下面是theValues 实际外观的示例:

[
  {
    data: [
            [25389, 24890, 49021, ...] <-- 30,000 elements
          ],
    schema: [
              {
                col: "id_number",
                type: "integer"
              }
            ]
  }
]

我看到的快速代码和慢速代码之间唯一有意义的区别是,慢速代码在每次迭代时都会访问 prop theValues,而快速代码不会触及 Vue 的任何复杂部分。 (它确实使用了theKeys,但即使我在函数内部创建了theKeys 的本地深层副本,性能也不会改变。)

基于此,问题似乎不是数据表组件无法处理我发送的数据量,或者嵌套循环本身效率太低。我最好的猜测是,从 props 中读取这么多内容会以某种方式减慢 Vue 本身的速度,但我不能 100% 确定这一点。

但我最终确实需要从道具中获取信息到表格中。我该怎么做才能以合理的速度加载?

【问题讨论】:

  • 我想知道它是否与访问道具有关。您可以尝试拉出循环不变部分吗?在循环之前const thePropData = theValues[0].data;,然后在循环中thePropData[i][j]。这有什么不同吗? (哦,还有迭代检查i &lt; thePropData.length
  • 为什么需要遍历theKeys 数组?为什么不使用 vuejs 提供的响应性?
  • 我已经尝试过类似的方法,将调用拉出内循环,而不是外循环。它没有任何区别,事后看来,这是因为在简化的示例中只有一列。把它拉到更上一层,从外循环中出来,就可以了。作为答案发布?
  • @Danizavtz 抱歉,我不明白你的建议是什么?

标签: javascript performance vue.js vuetify.js


【解决方案1】:

性能问题实际上是循环代码的症状,而不是 Vue。最昂贵的数据访问是在dataForDataTable() 的内部循环中:

for (i...) {
  for (j...) {
    theValues[0].data[i][j]  // ~50 ms average (expensive)
  }
}

// => long hang for 32K items

一种优化是将数组缓存在循环之外,这会显着提高循环执行时间并解决挂起问题:

const myData = theValues[0].data

for (i...) {
  for (j...) {
    myData[i][j]  // ~0.00145 ms average
  }
}

// => ~39 ms for 32K items

demo 1

请注意,使用 JavaScript API 可以在不使用循环的情况下计算相同的结果。这以轻微的性能成本(~1ms)提供了可读性和减少的代码行。具体来说,使用Array.prototype.mapdata中的每个值映射到一个对象属性,由Array.prototype.reducetheKeys上获得:

theValues[0].data
  .map(values => theKeys.reduce((obj,key,i) => {
    obj[key] = values[i]
    return obj
  }, {}))

// => ~40 ms for 32K items

demo 2

以上时间是在 2016 MacBook Pro - 2.7GHz i7、Chrome 87 上测得的。Codesandbox 演示可能与上述时间有很大差异。

【讨论】:

  • 可以确认在内部循环中直接访问theValues[0] 会杀死性能,但在循环外部分配它是黄金。不支持reduce,但这是个人喜好。
  • 看到这个答案我真的很惊讶。简单的数组访问(theValues[0].data)怎么会这么慢?但似乎你是对的,它与 Vue 反应性有关 - 你可以通过保留原始 dataForDataTable 代码和只留下 Object.freeze() 源数据(sampleData.js 在你的沙箱中)得到几乎相同的结果
【解决方案2】:

提示 1

原文:

访问道具 (data) 应该不是问题。是的,data 是反应性的,但阅读它应该非常有效(Vue 只是“记下”你正在使用该数据进行渲染)

好吧,看来我在这里显然错了......

您的组件正在通过 prop 获取数据,但数据很可能在父组件中是响应式的(来自 Vuex 或存储在父组件的 data 中)。 Vue 2 反应性系统的问题在于它基于 Object.defineProperty,并且该系统不允许拦截索引数组访问(Vue 无法检测像 arr[1] 这样的代码作为模板依赖项)。为了解决这个问题,如果对象属性(代码中的theValues[0].data)被访问,它会检查值是否是数组,如果是它会迭代整个数组(加上所有嵌套数组) to mark the items as dependencies -你可以阅读更深入的解释here

这个问题的一个解决方案是按照 tony19 的建议创建局部变量 let data = theValues[0].data。现在.data Vue getter 不是每次都被调用,性能是固定的...

但是如果你的数据是不可变的(永远不会改变),只需使用Object.freeze(),Vue 就不会尝试检测这些数据的变化。这不仅会使您的代码更快,而且在对象列表很大的情况下还可以节省 ton of memory

注意这个问题在 Vue 3 中得到修复,因为它使用了非常不同的基于 ES6 代理的反应系统...

提示 2

虽然 Vue computed 属性经过高度优化,但每次访问属性时仍有一些代码在运行(检查底层数据是否脏,计算的 prop 是否需要重新评估),如果你使用它,这项工作会加起来在你的情况下循环...... 在执行循环之前尝试制作 theKeys computed 属性的本地副本(浅拷贝就足够了,不需要深拷贝)

查看来自 Vue 核心成员的 really good video

当然,同样的问题也适用于从模板访问 dataForDataTable 计算属性。我鼓励您尝试使用 watcher 而不是 computed 来实现与 dataForDataTable 相同的逻辑并将其结果存储在 data 中,看看它是否有什么不同...

【讨论】:

    猜你喜欢
    • 2022-01-20
    • 1970-01-01
    • 2014-05-22
    • 2012-04-20
    • 2013-10-28
    • 2012-10-05
    • 2012-09-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多