小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. 虚拟 DOM
上一小节中提到,如果上面 <div> 元素中放的不是文本,而是其它多个元素这一情形,这时它们实际上就形成了一个 VNode Tree,也就是虚拟 DOM 了:
也就是说,虚拟 DOM 的每个节点都是一个 VNode,虚拟 DOM 就是由一个个的 VNode 组成的。
需要注意的是,虚拟 DOM 和 真实 DOM 在正常情况下(有组件时,虽然组件也会生成 VNode,但组件本身不会被渲染)不是一一对应的。
2. 插入 F 的案例
我们先来看一个案例:点击按钮,在字母列表中间插入字母 F:
<body> 中的代码如下:
<div id="app"></div>
<template id="my-app">
<ul>
<li v-for="item in letters">{{ item }}</li>
</ul>
<button @click="insertF">插入字母 F</button>
</template>
<script src="./js/vue.js"></script>
<script>
const App = {
data() {
return {
letters: ['A', 'B', 'C', 'D']
}
},
methods: {
insertF() {
this.letters.splice(2, 0, 'F');
}
},
template: '#my-app'
};
Vue.createApp(App).mount('#app');
</script>
在插入字母 F 时,数组发生了变化,页面因此会进行更新,我们可以确定的是,这次更新不会对 <ul> 和 <button> 元素进行更新,需要更新的是 <li> 元素列表,即应该插入一个 <li> 元素。
那么我们来思考这样一个问题:如何插入这个 <li> 元素,效率才是最高的呢?
- 方案
1:不管三七二十一先把原来的这几个<li>元素对应的DOM全删了,然后根据最新的<li>元素列表先生成新的VNodes,之后再重新渲染出真实DOM。显然,这样做效率很低。 - 方案
2:依次对比新旧VNodes,当和旧VNode不一样时才做相应变化(修改/新增/删除),之后只重新渲染有变化的VNodes。在本案例中,就是对比新旧VNodes时,原先的A、B元素都不变动,原先的C改为F,原先的D改为C,最后再新增一个VNodeD。这种做法相比于上面的做法性能高些,但仍不够好,因为看起来可以重复利用的C和D还是被做了修改,所以性能也不是很高。 - 方案
3:可能的拥有最高性能的方案是这样的:对比新旧VNodes,A、B、C、D都不要动,直接在中间插入F。如何实现呢?这就涉及到diff算法了。