虚拟dom与diff算法
1.虚拟dom
dom就是html文件里内容,一个页面由多个dom组成
<ul class="lists"> <li class="item">li1</li> <li class="item">li2</li> </ul>
而对应的虚拟dom是
tag: 'ul', attrs: { className: 'lists' }, children: [ { tag: 'li', attrs: { className: 'item' }, children: ['li1'] }, { tag: 'li', attrs: { className: 'item' }, children: ['li2'] } ]
tag
表示标签名,attrs
就是dom的属性,每个dom如果有children的话,就会在children中以数组的形式展示,数组的每一项就又是一个虚拟dom结构。
为什么要使用虚拟dom呢?
举个最简单的列子
使用jq的时候,使用append插入函数
要是后续改了某个值,要重新append.是整个dom发生的替换,并不是修改的那一项
并且单单一个空白的div底下的标签就有那么多
var div = document.createElement('div') var item, result = '' for (item in div) { result += ' | ' + item } console.log(result)
2.diff算法
什么是diff算法
我们在平时工作中,其实很多时候都会使用到diff算法
比如你在git提交代码的时候使用的 git diff
命令,再或者是网上的一些代码比对工具如svn上的,vue的key后续会说
而我们的虚拟dom,核心就是diff算法,我们前面讲过,找出有必要更新的节点更新,没有更新的节点就不要动。
这其中的核心就是如何找出哪些更新哪些不更新,这个过程就需要diff算法来完成
patch(container, vnode)
这个patch的过程是将一个vnode(vdom)添加到空容器生成真实dom的过程,主要的简化代码流程如下:
function creatElement(vnode) { let tag = vnode.tag let attrs = vnode.attrs || {} let children = vnode.children || [] // 无标签 直接跳出 if (!tag) { return null } // 创建元素 let elem = document.createElement(tag) // 添加属性 for(let attrName in attrs) { if (attrs.hasOwnProperty(attrName)) { elem.setAttribute(arrtName, arrts[attrName]) } } // 递归创建子元素 children.forEach((childVnode) => { elem.appendChild(createElement(childVnode)) }) return elem }
简化后的代码很简单,大家也都能够理解,其中的一个重要的点就是 自递归调用生成孩子节点,终止条件就是tag
为null
的情况
patch(vnode, newVnode)
这个patch过程就是比较差异的过程,我们这里就只模拟最简单的场景
// 简化流程 假设跟标签相同的两个虚拟dom function updateChildren (vnode, newVnode) { let children = vnode.children || [] let newChildren = newVnode.children || [] // 遍历现有的孩子 children.forEach((oldChild, index) => { let newChild = newChildren[index] if (newChild === null) { return } // 两者tag一样,值得比较 if (oldChild.tag === newChild.tag) { // 递归继续比较子项 updateChildren(oldchild, newChild) } else { // 两者tag不一样 replaceNode(oldChild, newChild) } }) }
这里面的点就也递归,这里只是简单的拿tag
来判断更新条件,其实实际的比这复杂很多很多;
而replace
函数实际的操作就是将newVnode
新生成的真实dom将老的dom替换掉,这里涉及更多的是原生dom操作,就不在赘述了。
key
属性。理想的 key
值是每项都有的唯一 id。index
(即数组的下标)来作为key
,但其实这是不推荐的一种使用方法之前的数据 之后的数据 key: 0 index: 0 name: test1 key: 0 index: 0 name: test1 key: 1 index: 1 name: test2 key: 1 index: 1 name: 不甘落后跑到第二的的一条数据 key: 2 index: 2 name: test3 key: 2 index: 2 name: test2 key: 3 index: 3 name: test3。
这样一来,追加数据以后,除了第一条数据能够就地复用
,后三条都要重新渲染,这显然不是我们想要的结果。
所以我们需要使用key来给每个节点做一个唯一标识,Vue的Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点,所以一句话,key的作用主要是为了高效的更新虚拟DOM
diff算法是用来比较虚拟dom差异的算法
v2主要依据首尾指针法。
按新旧头头 尾尾 头尾 尾头去比较,若是都不存在,则去寻找在旧dom是否有与当时新dom的key值一样的数据,若有则移动,无则新增一个
最后满足old子节点的头尾交叉,或new子节点的头尾交叉。说明对比完了。
此时如果old交叉,new未交叉,说明new子节点剩下的都是要新创建并插入。反之,old未交叉,new交叉,说明old子节点剩下的是多余的,需要从dom中移除。
v3 diff算法分为有key和无key
无key
1.新虚拟dom对旧虚拟dom进行替换,替换到最后
2.若新dom有剩余则新增
3.反之删除
有key
1.前序对比,新旧虚拟dom头头进行比对,直到不一样
2.后序对比新旧虚拟dom尾尾比对,直到不一样
3.新节点有剩余新增
4.旧节点有剩余移除
5.特殊情况无序(最长递增子序列算法)