Cookbook组件形式:优化 Vue 组件的运行时性能(3)

<!-- 节点可以展开或收起 --> <tree :data="data"></tree> <!-- 配置默认展开的节点 --> <tree :data="data" :default-expanded-keys="['1', '1-1']" @expanded-change="handleExpandedChange" > </tree>

优化后的性能耗时如下。

first rendering: 91.48193359375ms expanded change: 20.4287109375ms

你可以通过改进后的示例 (Demo3) 来观察组件的性能损耗。

冻结数据

到此为止,Tree 组件的性能问题已经不是很明显了。为了进一步扩大性能问题,查找优化空间。我们把节点数量增加到 10000 个。

// 生成 10000 个节点 this.getRandomData(4, 1000)

这里,我们故意制造一个可能存在性能问题的改动。虽然这不是必须的,当它能帮助我们了解接下来所要介绍的问题。

将计算属性 nodes 修改为在 data 的 watcher 中去获取 nodes 的值。

export default { // ... watch: { data: { handler() { this.nodes = this.getNodes(this.data); this.status = this.getStatus(this.nodes); }, immediate: true, }, // ... }, // ... };

这种修改对于实现的功能是没有影响的,那么性能情况如何呢。

first rendering: 490.119140625ms expanded change: 183.94189453125ms

使用 Performance 工具尝试查找性能瓶颈。

Cookbook组件形式:优化 Vue 组件的运行时性能

我们发现,在 getNodes 方法调用之后,有一段耗时很长的 proxySetter 。这是 Vue 在为 nodes 属性添加响应式,让 Vue 能够追踪依赖的变化。 getStatus 同理。

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

对象越复杂,层级越深,这个过程消耗的时间越长。当我们存在 1w 个节点时, proxySetter 的时间就会非常长了。

这里存在一个问题,我们不会对 nodes 某个具体的属性做修改,而是每当 data 变化时重新去计算一次。因此,这里为 nodes 添加的响应式是无用的。那么怎么把不需要的 proxySetter 去掉呢?一种方法是将 nodes 改回计算属性,一般情况下计算属性没有赋值行为。另一种方法就是冻结数据。

使用 Object.freeze() 来冻结数据,这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。

this.nodes = Object.freeze(this.getNodes(this.data));

查看 Performance 工具, getNodes 方法后已经没有 proxySetter 了。

Cookbook组件形式:优化 Vue 组件的运行时性能

性能指标如下,对于初次渲染的提升还是很可观的。

first rendering: 312.22998046875ms expanded change: 179.59326171875ms

你可以通过改进后的示例 (Demo4) 来观察组件的性能损耗。

那我们能否用同样的办法优化 status 的跟踪呢?答案是否定的,因为我们需要去更新 status 中的属性值 ( changeExpanded )。因此,这种优化只适用于其属性不会被更新,只会更新整个对象的数据。且对于结构越复杂、层级越深的数据,优化效果越明显。

替代方案

我们看到,示例中不管是节点的渲染还是数据的计算,都存在大量的循环或递归。对于这种大量数据的问题,除了上述提到的针对 Vue 的优化外,我们还可以从减少每次循环的耗时和减少循环次数两个方面进行优化。

例如,可以使用字典来优化数据查找。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/61999a5295843dec42d50951b45fe9f9.html