首先是Vue的组件的渲染方式-就地复用原则,官方的解释是这样的:

由于v-for循环渲染出的就是上面input这样的dom元素相同,只是内容有些不同,所以当Vue重新渲染列表时,并不会修改每个dom,因为修改dom的重绘和回流时很耗时的,所以它会用元素来匹配数据项,就地更改元素,并且保证他们每个索引位置能正确渲染。
这样利用元素就地复用减少dom操作,提高了渲染速度。但是这种方式只适用于不依赖子组件状态或临时 DOM 状态的列表。
如:
这个todo list的主要功能是添加,删除todo事项,以及事项的完成和撤销完成,并使用localStorage在客户端存储数据。
写完之后,我发现一个问题,就是当我点击列表第一项的checkbox表示完成这项todo时,下一项的checkbox被自动选中(此时原来的第一项已经移动到已完成列表,这一项变成第一项),我觉得很奇怪,测试了一番又发现,在已完成列表中取消完成也会有这种情况,总之点击之后checkbox的选中情况有点错乱。
这样一个功能,需要修改的节点位置没有改变,但是内容更新了,这虽然提高了复用性能,但是在复杂的表单会导致状态出现错位,也不会产生过度效果。那么这种情况怎么解决呢?
key:给列表中每一项添加一个唯一的用于区分别的列表项的一个key,所以对应的value就是唯一的。
作用:
更准确
因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。更快
利用key的唯一性生成map对象来获取对应节点,比遍历方式更快
虚拟dom到真实dom的隐射并反映,dom节点的比较需要用到diff算法
什么是diff算法?
我们需要渲染真实dom的时候往往会把生成一个虚拟节点 virtual DOM,当virtual dom某个节点的数据改变后生成一个新的Vnode,然后将Vnode和oldVnode对比,当然有不同的地方教就直接修改在真实DOM上,然后是oldVnode=Vnode
- 真实DOM
<div>
<p>123</p>
</div>- virtual DOM (虚拟DOM)
var Vnode={
tag:'div',
children:[{
tag:'p',text:'123'
}]
}diff的比较方式
在同层级进行,不会跨层级比较
oldDOM
<div>
<p>123</p>
</div>newDOM
<div>
<span>2222</span>
</div>
- 先对比DIV,发现两个DIV不对等
- 查看DIV的子元素P、SPAN,发现不对等
- 查看P、SPAN没有子元素,则移除P,增加SPAN

现在我们来看看在进行替换
对比节点函数
function patch(oldVnode, vnode) {
// 对比是否相等
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点
let parentEle = api.parentNode(oEl) // 父元素
createELe(vnode) // 为vnode生成新的元素
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新的元素添加到父元素中
api.removeChild(parentEle, oldVnode.el) // 移除以前的元素
}
}
return vnode
}判断两者是否相同函数
function sameVnode(a, b) {
return (
a.key === b.key && // 对比key
a.tag === b.tag && // 对比标签名
a.isComment === b.isComment && // 是否为注释节点
isDef(a.data) === isDef(b.data) && // 是否定义了data,或者其他属性
sameInputType(a, b) //判断是当<Input>时 是否type相同
)
}匹配规则
- 将Vnode的子节点Vch和oldVnode的子节点oldCh提取出来
- oldCh和vCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和vCh至少有一个已经遍历完了,就会结束比较。
图解


- 如果oldS和E匹配上了,那么真实DOM中的第一个节点会移到最后
- 如果oldE和S匹配上了,那么真实DOM中的最后一个节点会移到最后面,匹配上的两个指针向中间移动
- 如果四种匹配都没有一对成功成功的,那么遍历oldChild,S挨个和他们匹配,匹配成功就在真实dom中讲成功的节点移到最前面,如果没有成功,那么犟S对应的节点插入dom中对应的olds位置,olds和s指正向中间移动
- 如果4种比较都没匹配,如果设置了key,就会用key进行比较