Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue中的diff算法 #71

Open
TieMuZhen opened this issue Dec 21, 2021 · 0 comments
Open

Vue中的diff算法 #71

TieMuZhen opened this issue Dec 21, 2021 · 0 comments
Labels

Comments

@TieMuZhen
Copy link
Owner

TieMuZhen commented Dec 21, 2021

一、当数据发生变化时,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值来源。

参考文献

@TieMuZhen TieMuZhen added the Vue label Dec 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant