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

解析vue2.0的diff算法 #2

Open
aooy opened this Issue Mar 19, 2017 · 33 comments

Comments

Projects
None yet
@aooy
Owner

aooy commented Mar 19, 2017

作者:杨敬卓

转载请注明出处

目录

  • 前言
  • virtual dom
  • 分析diff
  • 总结

前言

vue2.0加入了virtual dom,有向react靠拢的意思。vue的diff位于patch.js文件中,我的一个小框架aoy也同样使用此算法,该算法来源于snabbdom,复杂度为O(n)。
了解diff过程可以让我们更高效的使用框架。
本文力求以图文并茂的方式来讲明这个diff的过程。

virtual dom

如果不了解virtual dom,要理解diff的过程是比较困难的。虚拟dom对应的是真实dom, 使用document.CreateElementdocument.CreateTextNode创建的就是真实节点。

我们可以做个试验。打印出一个空元素的第一层属性,可以看到标准让元素实现的东西太多了。如果每次都重新生成新的元素,对性能是巨大的浪费。

var mydiv = document.createElement('div');
for(var k in mydiv ){
  console.log(k)
}

virtual dom就是解决这个问题的一个思路,到底什么是virtual dom呢?通俗易懂的来说就是用一个简单的对象去代替复杂的dom对象。
举个简单的例子,我们在body里插入一个class为a的div。

var mydiv = document.createElement('div');
mydiv.className = 'a';
document.body.appendChild(mydiv);

对于这个div我们可以用一个简单的对象mydivVirtual代表它,它存储了对应dom的一些重要参数,在改变dom之前,会先比较相应虚拟dom的数据,如果需要改变,才会将改变应用到真实dom上。

//伪代码
var mydivVirtual = { 
  tagName: 'DIV',
  className: 'a'
};
var newmydivVirtual = {
   tagName: 'DIV',
   className: 'b'
}
if(mydivVirtual.tagName !== newmydivVirtual.tagName || mydivVirtual.className  !== newmydivVirtual.className){
   change(mydiv)
}

// 会执行相应的修改 mydiv.className = 'b';
//最后  <div class='b'></div>

读到这里就会产生一个疑问,为什么不直接修改dom而需要加一层virtual dom呢?

很多时候手工优化dom确实会比virtual dom效率高,对于比较简单的dom结构用手工优化没有问题,但当页面结构很庞大,结构很复杂时,手工优化会花去大量时间,而且可维护性也不高,不能保证每个人都有手工优化的能力。至此,virtual dom的解决方案应运而生,virtual dom很多时候都不是最优的操作,但它具有普适性,在效率、可维护性之间达平衡。

virtual dom 另一个重大意义就是提供一个中间层,js去写ui,ios安卓之类的负责渲染,就像reactNative一样。

分析diff

一篇相当经典的文章React’s diff algorithm中的图,react的diff其实和vue的diff大同小异。所以这张图能很好的解释过程。比较只会在同层级进行, 不会跨层级比较。

举个形象的例子。
<!-- 之前 -->
<div>           <!-- 层级1 -->
  <p>            <!-- 层级2 -->
    <b> aoy </b>   <!-- 层级3 -->   
    <span>diff</Span>
  </P> 
</div>

<!-- 之后 -->
<div>            <!-- 层级1 -->
  <p>             <!-- 层级2 -->
      <b> aoy </b>        <!-- 层级3 -->
  </p>
  <span>diff</Span>
</div>

我们可能期望将<span>直接移动到<p>的后边,这是最优的操作。但是实际的diff操作是移除<p>里的<span>在创建一个新的<span>插到<p>的后边。
因为新加的<span>在层级2,旧的在层级3,属于不同层级的比较。

源码分析

文中的代码位于aoy-diff中,已经精简了很多代码,留下最核心的部分。

diff的过程就是调用patch函数,就像打补丁一样修改真实dom。

function patch (oldVnode, vnode) {
	if (sameVnode(oldVnode, vnode)) {
		patchVnode(oldVnode, vnode)
	} else {
		const oEl = oldVnode.el
		let parentEle = api.parentNode(oEl)
		createEle(vnode)
		if (parentEle !== null) {
			api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
			api.removeChild(parentEle, oldVnode.el)
			oldVnode = null
		}
	}
	return vnode
}

patch函数有两个参数,vnodeoldVnode,也就是新旧两个虚拟节点。在这之前,我们先了解完整的vnode都有什么属性,举个一个简单的例子:

// body下的 <div id="v" class="classA"><div> 对应的 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
}

需要注意的是,el属性引用的是此 virtual dom对应的真实dom,patchvnode参数的el最初是null,因为patch之前它还没有对应的真实dom。

来到patch的第一部分,

if (sameVnode(oldVnode, vnode)) {
	patchVnode(oldVnode, vnode)
} 

sameVnode函数就是看这两个节点是否值得比较,代码相当简单:

function sameVnode(oldVnode, vnode){
	return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
}

两个vnode的key和sel相同才去比较它们,比如pspandiv.classAdiv.classB都被认为是不同结构而不去比较它们。

如果值得比较会执行patchVnode(oldVnode, vnode),稍后会详细讲patchVnode函数。

当节点不值得比较,进入else中

else {
		const oEl = oldVnode.el
		let parentEle = api.parentNode(oEl)
		createEle(vnode)
		if (parentEle !== null) {
			api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
			api.removeChild(parentEle, oldVnode.el)
			oldVnode = null
		}
	}

过程如下:

  • 取得oldvnode.el的父节点,parentEle是真实dom
  • createEle(vnode)会为vnode创建它的真实dom,令vnode.el =真实dom
  • parentEle将新的dom插入,移除旧的dom
    当不值得比较时,新节点直接把老节点整个替换了

最后

return vnode

patch最后会返回vnode,vnode和进入patch之前的不同在哪?
没错,就是vnode.el,唯一的改变就是之前vnode.el = null, 而现在它引用的是对应的真实dom。

var oldVnode = patch (oldVnode, vnode)

至此完成一个patch过程。

patchVnode

两个节点值得比较时,会调用patchVnode函数

patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
    	if (oldCh && ch && oldCh !== ch) {
	    	updateChildren(el, oldCh, ch)
	    }else if (ch){
	    	createEle(vnode) //create el's children dom
	    }else if (oldCh){
	    	api.removeChildren(el)
	    }
    }
}

const el = vnode.el = oldVnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。

节点的比较有5种情况

  1. if (oldVnode === vnode),他们的引用一致,可以认为没有变化。

  2. if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文本节点的比较,需要修改,则会调用Node.textContent = vnode.text

  3. if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用updateChildren函数比较子节点,这是diff的核心,后边会讲到。

  4. else if (ch),只有新的节点有子节点,调用createEle(vnode)vnode.el已经引用了老的dom节点,createEle函数会在老dom节点上添加子节点。

  5. else if (oldCh),新节点没有子节点,老节点有子节点,直接删除老节点。

updateChildren

updateChildren (parentElm, oldCh, newCh) {
    let oldStartIdx = 0, newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx
    let idxInOld
    let elmToMove
    let before
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
            if (oldStartVnode == null) {   //对于vnode.key的比较,会把oldVnode = null
                oldStartVnode = oldCh[++oldStartIdx] 
            }else if (oldEndVnode == null) {
                oldEndVnode = oldCh[--oldEndIdx]
            }else if (newStartVnode == null) {
                newStartVnode = newCh[++newStartIdx]
            }else if (newEndVnode == null) {
                newEndVnode = newCh[--newEndIdx]
            }else if (sameVnode(oldStartVnode, newStartVnode)) {
                patchVnode(oldStartVnode, newStartVnode)
                oldStartVnode = oldCh[++oldStartIdx]
                newStartVnode = newCh[++newStartIdx]
            }else if (sameVnode(oldEndVnode, newEndVnode)) {
                patchVnode(oldEndVnode, newEndVnode)
                oldEndVnode = oldCh[--oldEndIdx]
                newEndVnode = newCh[--newEndIdx]
            }else if (sameVnode(oldStartVnode, newEndVnode)) {
                patchVnode(oldStartVnode, newEndVnode)
                api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
                oldStartVnode = oldCh[++oldStartIdx]
                newEndVnode = newCh[--newEndIdx]
            }else if (sameVnode(oldEndVnode, newStartVnode)) {
                patchVnode(oldEndVnode, newStartVnode)
                api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
                oldEndVnode = oldCh[--oldEndIdx]
                newStartVnode = newCh[++newStartIdx]
            }else {
               // 使用key时的比较
                if (oldKeyToIdx === undefined) {
                    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
                }
                idxInOld = oldKeyToIdx[newStartVnode.key]
                if (!idxInOld) {
                    api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
                    newStartVnode = newCh[++newStartIdx]
                }
                else {
                    elmToMove = oldCh[idxInOld]
                    if (elmToMove.sel !== newStartVnode.sel) {
                        api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
                    }else {
                        patchVnode(elmToMove, newStartVnode)
                        oldCh[idxInOld] = null
                        api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
                    }
                    newStartVnode = newCh[++newStartIdx]
                }
            }
        }
        if (oldStartIdx > oldEndIdx) {
            before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
            addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
        }else if (newStartIdx > newEndIdx) {
            removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
        }
}

代码很密集,为了形象的描述这个过程,可以看看这张图。

过程可以概括为:oldChnewCh各有两个头尾的变量StartIdxEndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldChnewCh至少有一个已经遍历完了,就会结束比较。

具体的diff分析

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

diff的遍历过程中,只要是对dom进行的操作都调用api.insertBeforeapi.insertBefore只是原生insertBefore的简单封装。
比较分为两种,一种是有vnode.key的,一种是没有的。但这两种比较对真实dom的操作是一致的。

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

总结遍历过程,有3种dom操作:

  1. oldStartVnodenewEndVnode值得比较,说明oldStartVnode.el跑到oldEndVnode.el的后边了。

图中假设startIdx遍历到1。

  1. oldEndVnodenewStartVnode值得比较,说明 oldEndVnode.el跑到了newStartVnode.el的前边。(这里笔误,应该是“oldEndVnode.el跑到了oldStartVnode.el的前边”,准确的说应该是oldEndVnode.el需要移动到oldStartVnode.el的前边”)
  1. newCh中的节点oldCh里没有, 将新节点插入到oldStartVnode.el的前边。

在结束时,分为两种情况:

  1. oldStartIdx > oldEndIdx,可以认为oldCh先遍历完。当然也有可能newCh此时也正好完成了遍历,统一都归为此类。此时newStartIdxnewEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before的后边,before很多时候是为null的。addVnodes调用的是insertBefore操作dom节点,我们看看insertBefore的文档:parentElement.insertBefore(newElement, referenceElement)
    如果referenceElement为null则newElement将被插入到子节点的末尾。如果newElement已经在DOM树中,newElement首先会从DOM树中移除。所以before为null,newElement将被插入到子节点的末尾。
  1. newStartIdx > newEndIdx,可以认为newCh先遍历完。此时oldStartIdxoldEndIdx之间的vnode在新的子节点里已经不存在了,调用removeVnodes将它们从dom里删除。

下面举个例子,画出diff完整的过程,每一步dom的变化都用不同颜色的线标出。

  1. a,b,c,d,e假设是4个不同的元素,我们没有设置key时,b没有复用,而是直接创建新的,删除旧的。
  1. 当我们给4个元素加上唯一key时,b得到了的复用。

这个例子如果我们使用手工优化,只需要3步就可以达到。

总结

  • 尽量不要跨层级的修改dom
  • 设置key可以最大化的利用节点
  • diff的效率并不是每种情况下都是最优的
@userand

This comment has been minimized.

Show comment
Hide comment
@userand

userand commented Mar 23, 2017

@

@fengmiaosen

This comment has been minimized.

Show comment
Hide comment
@fengmiaosen

fengmiaosen commented Mar 24, 2017

mark

@cshenger

This comment has been minimized.

Show comment
Hide comment
@cshenger

cshenger Mar 24, 2017

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

cshenger commented Mar 24, 2017

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

@ascoders

This comment has been minimized.

Show comment
Hide comment
@ascoders

ascoders Mar 24, 2017

和 react 的几乎一样

ascoders commented Mar 24, 2017

和 react 的几乎一样

@aooy

This comment has been minimized.

Show comment
Hide comment
@aooy

aooy Mar 24, 2017

Owner

@ascoders 是的

Owner

aooy commented Mar 24, 2017

@ascoders 是的

@liuliangsir

This comment has been minimized.

Show comment
Hide comment

liuliangsir commented Mar 25, 2017

@aooy aooy added the javascript label Apr 4, 2017

@mofengfly

This comment has been minimized.

Show comment
Hide comment
@mofengfly

mofengfly Apr 9, 2017

赞,mark

mofengfly commented Apr 9, 2017

赞,mark

@iterry

This comment has been minimized.

Show comment
Hide comment
@iterry

iterry Apr 12, 2017

详细,点赞

iterry commented Apr 12, 2017

详细,点赞

@wangweida

This comment has been minimized.

Show comment
Hide comment
@wangweida

wangweida Apr 26, 2017

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

wangweida commented Apr 26, 2017

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

@aooy

This comment has been minimized.

Show comment
Hide comment
@aooy

aooy Apr 27, 2017

Owner

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

Owner

aooy commented Apr 27, 2017

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

@zengwenfu

This comment has been minimized.

Show comment
Hide comment
@zengwenfu

zengwenfu Apr 27, 2017

赞,有一处需要指出的是:
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)
)
}

zengwenfu commented Apr 27, 2017

赞,有一处需要指出的是:
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)
)
}

@aooy

This comment has been minimized.

Show comment
Hide comment
@aooy

aooy Apr 27, 2017

Owner

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

Owner

aooy commented Apr 27, 2017

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

@dean5277

This comment has been minimized.

Show comment
Hide comment
@dean5277

dean5277 Jun 9, 2017

配图满分,很好理解

dean5277 commented Jun 9, 2017

配图满分,很好理解

@linzb93

This comment has been minimized.

Show comment
Hide comment
@linzb93

linzb93 Jun 27, 2017

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

linzb93 commented Jun 27, 2017

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

@aooy

This comment has been minimized.

Show comment
Hide comment
@aooy

aooy Jul 3, 2017

Owner

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

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

aooy commented Jul 3, 2017

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

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

This comment has been minimized.

Show comment
Hide comment
@linzb93

linzb93 Jul 4, 2017

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

linzb93 commented Jul 4, 2017

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

@zoro-web zoro-web referenced this issue Jul 10, 2017

Closed

关闭 #2

@liz282907

This comment has been minimized.

Show comment
Hide comment
@liz282907

liz282907 commented Jul 22, 2017

react diff 几乎一样...

@aooy

This comment has been minimized.

Show comment
Hide comment
@aooy

aooy Jul 23, 2017

Owner

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

Owner

aooy commented Jul 23, 2017

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

@zhoujiamin

This comment has been minimized.

Show comment
Hide comment
@zhoujiamin

zhoujiamin Aug 3, 2017

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

zhoujiamin commented Aug 3, 2017

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

@ww522413622 ww522413622 referenced this issue Aug 27, 2017

Open

Vue | 源码学习 #6

0 of 1 task complete
@wqzwh

This comment has been minimized.

Show comment
Hide comment
@wqzwh

wqzwh Aug 30, 2017

学习了

wqzwh commented Aug 30, 2017

学习了

@HanYif

This comment has been minimized.

Show comment
Hide comment
@HanYif

HanYif commented Sep 18, 2017

mark

@lkdghzh

This comment has been minimized.

Show comment
Hide comment
@lkdghzh

lkdghzh commented Feb 2, 2018

mark

@alabihula

This comment has been minimized.

Show comment
Hide comment
@alabihula

alabihula Feb 7, 2018

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

alabihula commented Feb 7, 2018

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

@aooy

This comment has been minimized.

Show comment
Hide comment
@aooy

aooy Feb 9, 2018

Owner

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

Owner

aooy commented Feb 9, 2018

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

@cunjieliu

This comment has been minimized.

Show comment
Hide comment
@cunjieliu

cunjieliu commented Feb 24, 2018

👍

@jerryni

This comment has been minimized.

Show comment
Hide comment
@jerryni

jerryni May 7, 2018

感谢@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]

不知道我的理解对不~~

jerryni commented May 7, 2018

感谢@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]

不知道我的理解对不~~

@windlany

This comment has been minimized.

Show comment
Hide comment
@windlany

windlany May 19, 2018

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

windlany commented May 19, 2018

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

@sunchenguang

This comment has been minimized.

Show comment
Hide comment
@sunchenguang

sunchenguang May 29, 2018

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

sunchenguang commented May 29, 2018

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

@jxh150535011

This comment has been minimized.

Show comment
Hide comment
@jxh150535011

jxh150535011 Jul 27, 2018

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

jxh150535011 commented Jul 27, 2018

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

@LeeeeeeM

This comment has been minimized.

Show comment
Hide comment
@LeeeeeeM

LeeeeeeM Aug 17, 2018

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

LeeeeeeM commented Aug 17, 2018

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

@LeeeeeeM

This comment has been minimized.

Show comment
Hide comment
@LeeeeeeM

LeeeeeeM Aug 17, 2018

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

LeeeeeeM commented Aug 17, 2018

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

@tailorsira

This comment has been minimized.

Show comment
Hide comment
@tailorsira

tailorsira Aug 19, 2018

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
}

tailorsira commented Aug 19, 2018

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
}

@ivonzhang

This comment has been minimized.

Show comment
Hide comment
@ivonzhang

ivonzhang commented Sep 6, 2018

M

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment