虚拟DOM和diff算法密不可分,
虚拟dom,它本身就是一个 JavaScript 对象,为解决DOM操作非常耗时,把DOM转换为虚拟DOM,DOM操作转换为js计算,js执行速度较快。
diff算法在vue中被优化为O(n)的时间复杂度,diff算法是比较两个vnode,计算出最小的变更。
一,虚拟DOM
1.虚拟DOM结构
虚拟节点就是用一个对象来描述一个真实的DOM元素。首先将 template(真实DOM)先转成 ast, ast树通过 codegen生成 render函数, render函数里的 _c方法将它转为虚拟dom.
用js模仿DOM结构
<div id="div1" class="container"><p>vdom</p><ul style="font-size: 20px;"><li>a</li></ul>
</div>
转换为虚拟DOM对应的js对象:
{tag: 'div'props: {className: 'container'id: 'div1'}children: [{tag: 'p'children: 'vdom'}{tag: 'ul'props: {style="font-size: 20px;"}children: [{tag: 'li'children: 'a'}]}]
}
2.虚拟DOM如何生成
组件模板template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。
// 插值
const template = `<p>{{message}}</p>`
with(this){return _c('p',[createTextVNode(toString(message))])}
// h -> vnode
// createElement(_c) -> vnode
// this -> const vm = new Vue({....})
3.虚拟DOM解析过程
参照虚拟DOM结构
- 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象表示,包含 TagName、props 和 Children 等这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
- 当页面的状态发生改变,需要调整DOM结构,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
- 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。
二,diff算法
vue的vdom是模仿snabbdom写的,可以去github上看
1.diff算法的原理
● 只比较同级,不跨域比较;
● 如果tag不相同,直接删除重建,不再深度比较
● 如果tag和key都相同,默认是一样的节点,也不再深度比较
2.diff算法执行流程
1.首先用patch方法对比新旧vnode
patch参数有两种情况,一个参数是一个elementDOM元素,第二个是vnode,也有可能两个都是vnode,作用是第一个是直接渲染到一个空的DOM元素中,第二个是更新已有的内容
● 首先会处理下第一个参数如果不是vnode,那么会创建一个空的vnode,关联到这个DOM元素
● 然后判断是否是相同的vnode(通过判断两个vnode的key和select都相同)
如果都没有key,undefined===undefined //true。在循环体v-for必须传key
● 最后判断是不相同的两个vnode,就直接删掉重建,就不再对比了
2.然后在patch函数中调用patchVnode对比新旧children
patchVnode函数,接收两个参数,一个旧的vnode,一个新的vnode
tips:设置Vnode.elem:把旧的元素对应的DOM元素赋给新的,否则新的vDOM不知道更新哪个。
获取两个的children,然后把新旧children进行对比,主要是几种情况:
● 两者都有children的时候,我们要通过updateChilren通过key进行children之间的对比;
● 如果是新的children有值,旧的children没有值,我们要通过addVnode进行添加
● 如果是新的children没有值,旧的children有值,那我们要通过removeVnodes给移除掉
3.key的作用
不使用key,老的节点全部删掉,插入新的节点。使用key,会比对key,直接相同移过来就好了。key不能使用index,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2…这样排列,导致 Vue 会复用错误的旧子节点。