虚拟DOM(Virtual DOM)就是常说的虚拟节点,它是通过JS的Object对象模拟DOM中的节点,然后通过特定的render方法将其渲染成真实的DOM的节点。
真实DOM的代码:
<div>
<p>文本段p>
div>
VDOM的伪代码:
var Vnode = {
tag: 'div',
children: [{
tag: 'p',
text: '文本段'
}]
}
为什么要引入虚拟DOM?
解决浏览器性能问题,减少重排,提高性能。
先了解一下浏览器渲染页面的流程。
(1)HTML被HTML解析器解析成DOM Tree,CSS被CSS解析器解析成CSSOM Tree。(并行解析)
(2)DOM Tree和CSSOM Tree合并到一起,形成了render Tree(渲染树)。
重排:节点信息计算,即根据渲染树计算每个节点的几何信息(大小及位置)。
重绘:渲染绘制,即根据计算好的信息绘制整个页面,渲染出最终的页面。
前端性能优化——重排重绘.
总结: 每次真实 DOM 发生改变引起重排都会将上面的流程跑一遍,而重排过程特别是计算节点信息是非常消耗性能的,于是我们引入VDOM,在VDOM上进行的操作不会引起重排,然后再通过diff算法比较新VDOM(修改之后的)和旧 VDOM(修改前的)的不同从而去更新真实DOM(patch方法)。
注意: 特别要提一下 Vue 的 patch 是即时的,并不是打包所有修改最后一起操作DOM(React则是将更新放入队列后集中处理),这样岂不是相当于没有优化?实际上现代浏览器对这样的DOM操作做了优化,所以表现出来的结果是一样的,即减少的操作真实DOM的次数,达到减少重排,提高性能的目的。
Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
所以呢,VUE先在内存建一个虚拟DOM(Virtual DOM,简写VDOM),先把需要渲染的DOM元素放在这里,然后再放到浏览器上。
VDOM存放DOM元素是这样的,它将近似的元素进行复用,而不是重新创建一个元素,所以当浏览器需要用到两个近似的DOM元素时,其实用的是同一个DOM元素,只是有些属性值会进行对比修改。
看一个小案例:
<div id="app">
<span v-if="isUser">
<label for="username">用户账号label>
<input type="text" id="username" placeholder="用户账号">
span>
<span v-else>
<label for="email">用户邮箱label>
<input type="text" id="email" placeholder="用户邮箱">
span>
<button @click="isUser = !isUser">切换类型button>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
isUser: true
}
})
script>
当我们在用户账号的输入框内输入123时,突然想改类型,点击切换类型按钮时,输入框里面依旧是123。但是呢,此时的输入框id是email;而未切换之前的输入框的id是username。因为Vue内部会发现原来的input元素跟v-else中的input相似,因此直接拿来使用了。
如果我们不想重复使用这个input,只需使用key属性即可,但是key的属性值不能相同
<input type="text" id="username" placeholder="用户账号" key="username">
<input type="text" id="email" placeholder="用户邮箱" key="email">
先看一个简单的例子:
五个li 分别显示A,B,C,D,E,当我们要在B、C之间插入一个F,可以利用数组的splice(2,0,‘F’)。但是VUE系统怎么实现显示A,B,F,C,D,E呢?
diff算法默认执行起来是这样的:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
这样太耗性能,如果CDE后面跟的是一大堆东西的话,就要改动一大堆的数据,而我们只是想在BC中间插入一个F,除了位置,现有的东西都不想改变。
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
key的作用主要是为了高效的更新虚拟DOM
<li v-for="item in letters" :key="item">{{item}}li>
引入key,并且将item赋值给它,这样每一个li都有一个唯一的标识符。
因为key不同,所以VDOM不会复用之前的li,而是创建一个新的li再放到浏览器上。
现在又有一个新的问题,就是将index赋值给key可以吗?
用 index 作为 key,和没写基本上没区别,因为不管你数组的顺序怎么颠倒,index 都是 0, 1, 2 …这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作(类名、样式、指令,那么都会被全量的更新)。
在2,3,4中可以知道,key是相同的,所以VDOM会复用原先的li,但是他们所带的值却不同,所以又需要改变值,那这样跟没写key一样了。(看不懂可以再回去看diff的算法)
注: 用组件唯一的 id(一般由后端返回)作为它的 key,实在没有的情况下,可以在获取到列表的时候通过某种规则为它们创建一个 key,并保证这个 key 在组件整个命周期中都保持稳定。
代码:
<div id="app">
<ul>
<li v-for="(item,index) in letters" :key="item">{{item}}li>
ul>
<button @click="addF">加Fbutton>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
letters: ['A', 'B', 'C', 'D', 'E']
},
methods: {
addF() {
this.letters.splice(2, 0, 'F');
}
}
})
script>