Skip to content

Vue中的diff算法 #71

@TieMuZhen

Description

@TieMuZhen

一、当数据发生变化时,vue是怎么更新节点的?

我们先根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后VnodeoldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode

diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。

二、virtual DOM和真实DOM的区别?

virtual DOM是将真实的DOM的数据抽取出来,以对象的形式模拟树形结构。比如dom是这样的:

<div>
    <p>123</p>
</div>

对应的virtual DOM(伪代码):

var Vnode = {
    tag: 'div',
    children: [
        { tag: 'p', text: '123' }
    ]
};

(温馨提示:VNodeoldVNode都是对象,一定要记住)

三、diff的比较方式?

在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较,新旧节点各有两个头尾的指针StartIdxEndIdx,如果设置了key,就会用key进行比较,在比较的过程中,指针会往中间靠,一旦StartIdx>EndIdx表明新旧节点至少有一个已经遍历完了,就会结束比较。

<div>
    <p>123</p>
</div>

<div>
    <span>456</span>
</div>

上面的代码会分别比较同一层的两个div以及第二层的p和span,但是不会拿div和span作比较。

比较的几种情况

  • if (oldVnode === vnode),他们的引用一致,可以认为没有变化。
  • if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文本节点的比较,需要修改,则会调用Node.textContent = vnode.text
  • if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用updateChildren函数比较子节点,这是diff的核心
  • else if (ch),只有新的节点有子节点,调用createEle(vnode)vnode.el已经引用了老的dom节点,createEle函数会在老dom节点上添加子节点。
  • else if (oldCh),新节点没有子节点,老节点有子节点,直接删除老节点。

四、key的作用

设置key和不设置key的区别:

不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中
查找匹配的节点,所以为节点设置key可以更高效的利用dom

如我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:

即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?

所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。

所以一句话,key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

五、为什么不能用index作为key?

const list = [
    {
        id: 1,
        name: "Person1"
    },
    {
        id: 2,
        name: "Person2"
    },
    {
        id: 3,
        name: "Person3"
    },
    {
        id:4,
        name:"Person4"
    }
];

此时,删除 “Person4” 是正常的,但是如果我删除 “Person2” 就会出现问题

删除前

删除后

这个时候,除了Person1之外,剩下的Person3Person4,因为被发现与相应key的绑定关系有变化,所以被重新渲染,这会影响性能。
如果此时listitemselect的选项,其中Person3是选中的,这个时候Person2被删除了,用index作为key就会变成是 Person4选中的了,这就产生了bug。

如果使用唯一id作为key,删除Person2后,剩下的元素因为与key的关系没有发生变化,都不会被重新渲染,从而达到提升性能的目的。此时,listitem作为select的选项,也不会出现上面所描述的bug。数据库中每一条数据都会一个id作为唯一标识,而这个id也是我们最常使用作为key值来源。

参考文献

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions