1.什么是虚拟DOM
用JS按照DOM结构来实现的树形结构对象,包含了tag、props、children三个属性
- tag: 指定元素的标签类型,如’li’, ‘div’, 'a’等
- props: 表示指定元素身上的属性,如class, style, 自定义属性等
- children: 表示指定元素是否有子节点,参数以数组的形式传入
// HTML
<div id="app">
<p class="text">hello world!</p>
</div>
// 上面的 HTML 转换成虚拟DOM 如下:
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello world!'
]
}
]
}
2.为什么需要虚拟DOM
既然已经有了DOM,为什么还需要额外加一层抽象?
- 为了尽可能少的操作DOM,不仅仅是DOM相对较慢,更因为频繁操作DOM会造成浏览器的重绘或者回流,这些都是性能的杀手。因此,我们需要这一层抽象,在patch过程中尽可能的一次性将差异更新到DOM中。
- 虚拟DOM最初的目的,就是更好的跨平台,比如Node.js就没有DOM,如果想实现SSR(服务端渲染),那么一个方式就是借助Virtual DOM,因为Virtual DOM本身是JavaScript对象.
3.真实DOM和虚拟DOM的区别
1.虚拟DOM不会进行排版与重绘操作
2.真实DOM频繁排版与重绘的效率是相当低的
3.虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
4.虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部
4.整个DOM-diff的过程(vue的渲染过程)
-
用js对象模拟DOM(虚拟DOM)
利用 createElement 方法创建 VNode,每个 VNode 有 children,children 每个元素也是一个 VNode,这样就形成了一个 VNode Tree,它很好的描述了我们的 DOM Tree -
把此虚拟DOM转成真实DOM并插入页面中(render)
-
如果有事件发生修改了虚拟DOM,比较两颗虚拟DOM树的差异,得到差异对象(diff)
-
把差异对象应用到真正的DOM树上(patch)
5.虚拟DOM的Diff
-
当数据发生变化时,vue是怎么更新节点的
先根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后Vnode和oldVnode做对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode。
diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。 -
diff的比较方式
在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。 -
流程
当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图。
-
比较的过程
我们可以假设有oldVnode和Vnode这两个数组,而且有四个变量充当指针分别指到两个数组的头尾.。重复下面的对比过程,直到两个数组中任一数组的头指针超过尾指针,循环结束:
-
头头对比: 对比两个数组的头部,如果找到,把新节点patch到旧节点,头指针后移
-
尾尾对比: 对比两个数组的尾部,如果找到,把新节点patch到旧节点,尾指针前移
-
旧尾新头对比: 交叉对比,旧尾新头,如果找到,把新节点patch到旧节点,旧尾指针前移,新头指针后移
-
旧头新尾对比: 交叉对比,旧头新尾,如果找到,把新节点patch到旧节点,新尾指针前移,旧头指针后移
-
利用key对比: 用新指针对应节点的key去旧数组寻找对应的节点,这里分三种情况,当没有对应的key,那么创建新的节点,如果有key并且是相同的节点,把新节点patch到旧节点,如果有key但是不是相同的节点,则创建新节点