发表于: 2019-11-25 23:10:56
1 976
今日完成的事
调试签到接口
明日计划的事
调试消息接口
收获
Vue列表渲染性能优化原理
Vue 是一个高效的 mvvm 框架,这得益于作者已经帮我们框架内部做了足够的优化,比如各个细节的缓存( parseText 结果的缓存,compile 编译结果的缓存等)。
大列表是容易造成性能问题的地方,一不小心就会造成大量的重绘和重排。Vue 的列表渲染实现在 v-for 指令的 update 方法, 性能优化的大部分细节在 diff 函数。
列表渲染时会为迭代列表的每一份数据并为他们生成各自对应的片段对象 frag ,frag.node 为片段对象的 DOM 元素。
frag缓存和track-by
在列表渲染过程中,当列表数据发生变化时,为了避免 frag 的重复创建和大规模的重新渲染, Vue 会尽可能复用缓存的 frag ,高效的缓存 frag 命中率也是 DOM 元素复用的关键。
例子
`, data: function(){ return { model: [1, 2, 3] } } })
当这个组件中的列表首次渲染时,Vue 会将创建的 frag 缓存到 dir.cache 。默认通过数据对象的特征来决定对已有作用域和 DOM 元素的复用程度。例如当数据对象为Array时,缓存 id 为数组的 value ,当数据对象为 Object 时,缓存 id 为对象的 $key 。对于这个例子来说三个缓存 id 为1、2、3。
这样在上面的例子中,如果 vm.model 变为 [3, 2, 1],新的列表的三个片段的缓存 id 分别为3、2、1,因此我们能做到复用全部已创建的frag。
v-for 指令中片段 frag 的缓存 id 计算规则在 getTrackByKey ,从中可以看到,当 track-by 不存在时,缓存 id 将取数组的 value 或对象的 key 。但是这里有一个问题,如果数组出现重复值,会出现缓存 id 冲突的警告。副作用就是会忽略重复的片段,这是因为相同的缓存 id 获取的 frag 将会引用同一个 DOM 元素。当该 DOM 元素在复用算法中处理过一次后会将 frag 的 reused 属性变为 false ,这就导致 v-for 指令会重新尝试将该插入到DOM中,然而因为文档中已经存在该 DOM 元素,就会导致插入失败。
这时 Vue 提示我们可以使用 track-by='$index' ,它将使用数据的索引作为缓存 id ,索引作为缓存id一定是唯一的,但同时新旧数据的相同索引的缓存 id 是相同的,所使用的 frag 也是同一份。这回导致新列表的的 frags 失去和数据顺序的映射关系。而 frag.node 的数据在flushBatcherQueue更新。因为这种更新列表的方式不会移动DOM元素,而是在原位更新,简单地以对应索引的新值刷新,并不会受重复值的影响,这让数据替换非常高效,但同时也会有副作用,比如如果片段存有私有数据或状态,原位更新模式并不会同步状态。如下例
[track-by='index'副作用]
在第一个例子中,没有开启 track-by='$index' , v-for 指令会根据数组的值作为缓存 id 缓存每项 frag,当反转列表的顺序是,Vue 会根据缓存 id 为列表的每一项取出可复用的frag,但是 frags 的顺序也是反转的,Vue 会通过DOM元素移动算法将每个片段移动到正确的位置,因此当 input 有输入时,因为整个 DOM 节点发生了移动,input 的输入内容并没有错乱,这意味着我们没有丢失 frag 内 DOM 元素的私有状态。
再看第二个例子,开启了 track-by='$index' 之后,v-for 指令会根据数据的索引作为缓存 id 缓存每项片段,当反转列表的顺序时,每项的frag 会复用以索引为缓存id所缓存的 frag,所以生成的 frags 和原列表的 frags 是一样的,根据DOM元素移动算法,列表的 DOM 节点并没有移动,每个片段的数据更新会在接下来的流程中更新,所以会发现每个片段的数据更新了,但是因为 DOM 元素节点没有移动,因此每个 DOM 节点中input的输入状态并没有根据元素的变化而更新。
track-by='$index' 是一种简洁高效的优化手段,但是使用的时候你必须明白他做了什么。
在一些富交互的列表使用 track-by=‘$index’ 需要格外谨慎,但是在非 track-by=‘$index’模式我们仍可通过 track-by 尽量优化。有时候 v-for 指令并不能最大化优化,
默认情况 v-for 指令对于对象数据会将对象的键作为缓存 id,上面的例子发现列表更新后,对象的键没有重复,所以导致缓存一个都没有命中,而列表更新的结果也是重新渲染整个列表。但上面例子很明显可以看出,如果能够将对象的 a.id、b.id、c.id 作为缓存 id ,那么当列表更新时,所有缓存都能够命中,甚至连DOM元的移动都不需要, track-by='id' 就是做这样的事情.
评论