Coder Social home page Coder Social logo

解析vue2.0的diff算法 about blog HOT 53 OPEN

aooy avatar aooy commented on August 22, 2024 173
解析vue2.0的diff算法

from blog.

Comments (53)

zengwenfu avatar zengwenfu commented on August 22, 2024 8

赞,有一处需要指出的是:
function sameVnode(oldVnode, vnode){
return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
}
vue中sameVnode不会比较vnode.sel===oldVnode.sel,按照定义,这里的sel是选择器,如果选择器不一致就不值得比较的话,那么vue里面的v-bind:class动态绑定class就变得完全不提倡使用了,事实上vue并没有做这个比较:
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
}

from blog.

cshenger avatar cshenger commented on August 22, 2024 7

配图好评,看代码绝对会晕的

from blog.

aooy avatar aooy commented on August 22, 2024 3

@zengwenfu 谢谢指正,本文从原始算法去解读vue的diff,没有完全契合vue,vue实际情况会更复杂,它考虑到了class的动态绑定和input的type值,对sameVnode进行了修改。

from blog.

sunchenguang avatar sunchenguang commented on August 22, 2024 2

@jerryni 按源码的话你讲的对,同理后面加key的方案也不会有第4步移动c的操作

from blog.

LeeeeeeM avatar LeeeeeeM commented on August 22, 2024 2

@jxh150535011 react是批量更新Component,做完整个Diff之后再做DOM操作。而Vue是即时移动或操作DOM,需要两个数组维护startIndex 和 endIndex。

from blog.

aooy avatar aooy commented on August 22, 2024 1

@linzb93 一层一层套的是vnode的children属性,el存的是此vnode渲染出来的Element对象,也就是真实节点。
例如:

<body>
 <div></div>
<body>
//对应的vnode
{
  el:  document.body,
  children: [
        {
            el:  document.body.children[0],
            children: [],
            ...
        }
  ], 
  ...
}

from blog.

aooy avatar aooy commented on August 22, 2024 1

@alabihula 我觉得之前第三点的说法不妥当,所以改啦,很多时候明显自己操作dom会快很多,但是框架本身就是为了方便开发和协作的,跳出框架去做这些优化反而会增加维护的成本。

from blog.

hdulsh avatar hdulsh commented on August 22, 2024 1

感谢@aooy分享,很赞的文章~~

不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
img

第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))这个逻辑:

patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

不知道我的理解对不~~

大佬 加上key的那张图 c是不是也不需要移动啊

from blog.

userand avatar userand commented on August 22, 2024

@

from blog.

fengmiaosen avatar fengmiaosen commented on August 22, 2024

mark

from blog.

ascoders avatar ascoders commented on August 22, 2024

和 react 的几乎一样

from blog.

aooy avatar aooy commented on August 22, 2024

@ascoders 是的

from blog.

 avatar commented on August 22, 2024

React 的 diff 算法

from blog.

mofengfly avatar mofengfly commented on August 22, 2024

赞,mark

from blog.

iterry avatar iterry commented on August 22, 2024

详细,点赞

from blog.

wangweida avatar wangweida commented on August 22, 2024

写的很好,点个赞。
当oldEndVnode,newStartVnode值得比较,说明 oldEndVnode.el跑到了newStartVnode.el的前边。
这句话估计你写笔误了吧...

from blog.

aooy avatar aooy commented on August 22, 2024

@wangweida 是的笔误了,谢谢提醒。

from blog.

dean5277 avatar dean5277 commented on August 22, 2024

配图满分,很好理解

from blog.

linzb93 avatar linzb93 commented on August 22, 2024

有个疑问,假如一个节点是根节点的好几层子节点,那么它对应的vnode的el属性应该怎么表示?是从根节点一层层套下去吗?我看了你的代码还没明白。

from blog.

linzb93 avatar linzb93 commented on August 22, 2024

@aooy 我也有这么想过,就是会担心性能问题(我怎么会担心这个→_→),看来真的是这样的。谢谢了。。

from blog.

liz282907 avatar liz282907 commented on August 22, 2024

react diff 几乎一样...

from blog.

aooy avatar aooy commented on August 22, 2024

@liz282907 这篇文章学习过的,有借鉴的地方。vue和react的diff就当是黑猫白猫吧

from blog.

zhoujiamin avatar zhoujiamin commented on August 22, 2024

react虚拟DOM算法 和vue 的这个有什么不同呢?

from blog.

wqzwh avatar wqzwh commented on August 22, 2024

学习了

from blog.

HanYif avatar HanYif commented on August 22, 2024

mark

from blog.

lkdghzh avatar lkdghzh commented on August 22, 2024

mark

from blog.

alabihula avatar alabihula commented on August 22, 2024

有一点疑问搜了好多也没有特别明白的回答,就是后面说的三个优化点,具体能有个例子么,尤其是手动优化diff

from blog.

cunjieliu avatar cunjieliu commented on August 22, 2024

👍

from blog.

jerryni avatar jerryni commented on August 22, 2024

感谢@aooy分享,很赞的文章~~

不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
img

第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))这个逻辑:

patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

不知道我的理解对不~~

from blog.

windlany avatar windlany commented on August 22, 2024

我有个地方不太明白,看代码中patchVnode中的updateChildren又调用了patchVnode,这应该是递归,为什么说是一层一层的比较呢?还是有想问一下最开始传入patch的Vnode是新虚拟数的root节点吗?

from blog.

jxh150535011 avatar jxh150535011 commented on August 22, 2024

vue 为什么要使用 两头并行递减判断,这一步的优化 主要是为了解决什么问题?
实际上 按照普通的diff判断 大家都从index=0 开始就好了 跟 react diff 类似即可 ,这样代码可读性也高很多,start 、 end 做交叉判断 主要是为了解决什么情况呢?

from blog.

LeeeeeeM avatar LeeeeeeM commented on August 22, 2024

@windlany Vue讲究复用DOM,尽量少创建或删除DOM,所以它的DOM操作是即时的,你可能会有疑问,不是批量操作会性能会高吗? 因为现在浏览器做了优化,两者差距不大了。 所以一层一层递归操作,没有问题。

from blog.

malaxiannv avatar malaxiannv commented on August 22, 2024

function sameVnode(oldVnode, vnode){
return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
}
这里面的key是什么?我看上面你写的vnode的定义中并没有key属性。

// body下的

对应的 oldVnode 就是

{
el: div //对真实的节点的引用,本例中就是document.querySelector('#id.classA')
tagName: 'DIV', //节点的标签
sel: 'div#v.classA' //节点的选择器
data: null, // 一个存储节点属性的对象,对应节点的el[prop]属性,例如onclick , style
children: [], //存储子节点的数组,每个子节点也是vnode结构
text: null, //如果是文本节点,对应文本节点的textContent,否则为null
}

from blog.

ivonzhang avatar ivonzhang commented on August 22, 2024

M

from blog.

caoyongqiang avatar caoyongqiang commented on August 22, 2024

@aooy 赞!感谢分享,有一处需要指正
对updateChildren函数中的 oldStartIdx > oldEndIdx 情况的分析是不够准确的

此时newStartIdx和newEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before的后边,before很多时候是为null的

看一下before的定义
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
before并不是很多时候是null的,只要newEndIdex发生过左移,newCh[newEndIdx + 1]就不是null,因此before就不是null。同时before不为null的情况下,也不是插在before的后面,而是插在before的前面。
举个例子:oldCh = [a, b],newCh = [a, c, b]。updateChildren中的while循环对比结束后,before指向的是b。插入操作是把c插入到b前面。

from blog.

sandlover avatar sandlover commented on August 22, 2024

对着源码看了下,豁然洞开,赞👍

from blog.

136shine avatar 136shine commented on August 22, 2024

请问,patchVnode 中的 updateEle(el, vnode, oldVnode) 的含义是啥,看了下该函数,应该是为节点设置class、ID、data等属性,谢谢

from blog.

Ray-56 avatar Ray-56 commented on August 22, 2024

mark

from blog.

opop007 avatar opop007 commented on August 22, 2024

mark,感谢

from blog.

mumofa avatar mumofa commented on August 22, 2024

看完了 一开始看得很头疼 特别是updateChildren 那里
不过慢慢啃下来 有种豁然开朗的感觉
谢谢up~

from blog.

1003047593 avatar 1003047593 commented on August 22, 2024

题主对diff算法解析的真是到位,虽然diff不是最优解,但是综合来看,易推广

from blog.

haaling avatar haaling commented on August 22, 2024

感谢@aooy分享,很赞的文章~~

不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
img

第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))这个逻辑:

patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

不知道我的理解对不~~

我也是这样理解的第四步应该不用插入操作,因为比较的是oldEndVnode和newEndVnode。
然后oldEndIdx前移,结束,将oldEnd和oldStart之间的元素(含)remove。不对还请指正

对于与sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)为true的情况,不需要对dom进行移动。

from blog.

niujiangyao avatar niujiangyao commented on August 22, 2024

image
这个b不会复用这块,看源码如果没有设置key的话 他也会去遍历oldch找到b然后复用的吧,只是有没有key查找的速度不一样 有key的话是map映射 查找比遍历快

from blog.

YeYongFen avatar YeYongFen commented on August 22, 2024

感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
img
第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))这个逻辑:

patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

不知道我的理解对不~~

我也是这样理解的第四步应该不用插入操作,因为比较的是oldEndVnode和newEndVnode。 然后oldEndIdx前移,结束,将oldEnd和oldStart之间的元素(含)remove。不对还请指正

对于与sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)为true的情况,不需要对dom进行移动。

感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
img
第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))这个逻辑:

patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

不知道我的理解对不~~

我也是这样理解的第四步应该不用插入操作,因为比较的是oldEndVnode和newEndVnode。 然后oldEndIdx前移,结束,将oldEnd和oldStart之间的元素(含)remove。不对还请指正

对于与sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)为true的情况,不需要对dom进行移动。

同意你的观点,这时候应该是 oldEndVnode== newEndVnode 这种情况。不需要移动 dom节点
image

from blog.

YeYongFen avatar YeYongFen commented on August 22, 2024

你好,我发现你的第一个例子,也就是 不带key的例子有点不合理

idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
  function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }

由于没有key ,那么肯定是走 findIdxInOld。

例如第一个点 b,通过 sameVnode 它还是可以找到 oldch 里面的 d 的啊,所以第一步不会是插入。

这是我的代码

    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app" @click="change">
            <component :is="'h'+mm[n]" v-for="n in arr" >{{n}}</component>
        </div>
    </body>
    <script type="text/javascript">
        let s = 1;
        var app = new Vue({
            el: '#app',
            data() {
                return {
                    arr: ["a","b","c","d"],
                    tag:"span",
                    mm:{
                        "a":1,"b":2,"c":3,"d":4,"e":5
                    }
                }
            },
            methods: {
                change() {
                    this.arr = ["b","e","d","c"];
                    //this.tag = "h"+ s++;
                }
            }

        })
    </script>

from blog.

xubaifuCode avatar xubaifuCode commented on August 22, 2024
idxInOld = oldKeyToIdx[newStartVnode.key]
if (!idxInOld) {
    api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
    newStartVnode = newCh[++newStartIdx]
}

idxInOld 有可能是0吧?

另外我结合大佬的文章和国外一个讲虚拟DOM的把两者结合实现了一个可直观感受且跑得通得DEMO。请看:https://github.com/xubaifuCode/virtual-dom-and-diff-implementation

from blog.

haoolii avatar haoolii commented on August 22, 2024

感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
img
第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))这个逻辑:

patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]

不知道我的理解对不~~

大佬 加上key的那张图 c是不是也不需要移动啊

一開始看圖真的看不懂 直接看源碼還清楚些 一直碎碎念為何C要移動
原來家都有這疑惑哈哈

from blog.

GitHdu avatar GitHdu commented on August 22, 2024

你好,我发现你的第一个例子,也就是 不带key的例子有点不合理

idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
  function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }

由于没有key ,那么肯定是走 findIdxInOld。

例如第一个点 b,通过 sameVnode 它还是可以找到 oldch 里面的 d 的啊,所以第一步不会是插入。

这是我的代码

    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app" @click="change">
            <component :is="'h'+mm[n]" v-for="n in arr" >{{n}}</component>
        </div>
    </body>
    <script type="text/javascript">
        let s = 1;
        var app = new Vue({
            el: '#app',
            data() {
                return {
                    arr: ["a","b","c","d"],
                    tag:"span",
                    mm:{
                        "a":1,"b":2,"c":3,"d":4,"e":5
                    }
                }
            },
            methods: {
                change() {
                    this.arr = ["b","e","d","c"];
                    //this.tag = "h"+ s++;
                }
            }

        })
    </script>

vue版本不一样,楼主分析的是老版本,还没有这个函数

from blog.

Inakiz avatar Inakiz commented on August 22, 2024

Mark

from blog.

wolongfeitian avatar wolongfeitian commented on August 22, 2024

为什么不设key,newCh和oldCh只会进行头尾两端的相互比较,这样怎么保证更新的dom正确呢?

from blog.

xsfxtsxxr avatar xsfxtsxxr commented on August 22, 2024

对于vnode.key的比较,会把oldVnode = null

请问这句话怎么理解啊?

from blog.

uaoin avatar uaoin commented on August 22, 2024

@aooy 赞!感谢分享,有一处需要指正 对updateChildren函数中的 oldStartIdx > oldEndIdx 情况的分析是不够准确的

此时newStartIdx和newEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before的后边,before很多时候是为null的

看一下before的定义 before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el before并不是很多时候是null的,只要newEndIdex发生过左移,newCh[newEndIdx + 1]就不是null,因此before就不是null。同时before不为null的情况下,也不是插在before的后面,而是插在before的前面。 举个例子:oldCh = [a, b],newCh = [a, c, b]。updateChildren中的while循环对比结束后,before指向的是b。插入操作是把c插入到b前面。

newStartIdxnewEndIdx之间新增的节点为什么要插入在newEndIdx之后呢?
不应该是插入到它们之间?

仔细想了想 确实应该插入到newEndIdx之后,因为循环结束后 包括newStartIdx和newEndIdx都是新增的节点

from blog.

Related Issues (7)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.