Comments (53)
赞,有一处需要指出的是:
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.
配图好评,看代码绝对会晕的
from blog.
@zengwenfu 谢谢指正,本文从原始算法去解读vue的diff,没有完全契合vue,vue实际情况会更复杂,它考虑到了class的动态绑定和input的type值,对sameVnode进行了修改。
from blog.
@jerryni 按源码的话你讲的对,同理后面加key的方案也不会有第4步移动c的操作
from blog.
@jxh150535011 react是批量更新Component,做完整个Diff之后再做DOM操作。而Vue是即时移动或操作DOM,需要两个数组维护startIndex 和 endIndex。
from blog.
@linzb93 一层一层套的是vnode的children属性,el存的是此vnode渲染出来的Element对象,也就是真实节点。
例如:
<body>
<div></div>
<body>
//对应的vnode
{
el: document.body,
children: [
{
el: document.body.children[0],
children: [],
...
}
],
...
}
from blog.
@alabihula 我觉得之前第三点的说法不妥当,所以改啦,很多时候明显自己操作dom会快很多,但是框架本身就是为了方便开发和协作的,跳出框架去做这些优化反而会增加维护的成本。
from blog.
感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了
else if(sameVnode(oldEndVnode, newEndVnode))
这个逻辑:patchVnode(oldEndVnode, newEndVnode) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx]不知道我的理解对不~~
大佬 加上key的那张图 c是不是也不需要移动啊
from blog.
@
from blog.
mark
from blog.
和 react 的几乎一样
from blog.
@ascoders 是的
from blog.
from blog.
赞,mark
from blog.
详细,点赞
from blog.
写的很好,点个赞。
当oldEndVnode,newStartVnode值得比较,说明 oldEndVnode.el跑到了newStartVnode.el的前边。
这句话估计你写笔误了吧...
from blog.
@wangweida 是的笔误了,谢谢提醒。
from blog.
配图满分,很好理解
from blog.
有个疑问,假如一个节点是根节点的好几层子节点,那么它对应的vnode的el属性应该怎么表示?是从根节点一层层套下去吗?我看了你的代码还没明白。
from blog.
@aooy 我也有这么想过,就是会担心性能问题(我怎么会担心这个→_→),看来真的是这样的。谢谢了。。
from blog.
react diff 几乎一样...
from blog.
@liz282907 这篇文章学习过的,有借鉴的地方。vue和react的diff就当是黑猫白猫吧
from blog.
react虚拟DOM算法 和vue 的这个有什么不同呢?
from blog.
学习了
from blog.
mark
from blog.
mark
from blog.
有一点疑问搜了好多也没有特别明白的回答,就是后面说的三个优化点,具体能有个例子么,尤其是手动优化diff
from blog.
👍
from blog.
感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))
这个逻辑:
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
不知道我的理解对不~~
from blog.
我有个地方不太明白,看代码中patchVnode中的updateChildren又调用了patchVnode,这应该是递归,为什么说是一层一层的比较呢?还是有想问一下最开始传入patch的Vnode是新虚拟数的root节点吗?
from blog.
vue 为什么要使用 两头并行递减判断,这一步的优化 主要是为了解决什么问题?
实际上 按照普通的diff判断 大家都从index=0 开始就好了 跟 react diff 类似即可 ,这样代码可读性也高很多,start 、 end 做交叉判断 主要是为了解决什么情况呢?
from blog.
@windlany Vue讲究复用DOM,尽量少创建或删除DOM,所以它的DOM操作是即时的,你可能会有疑问,不是批量操作会性能会高吗? 因为现在浏览器做了优化,两者差距不大了。 所以一层一层递归操作,没有问题。
from blog.
function sameVnode(oldVnode, vnode){
return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
}
这里面的key是什么?我看上面你写的vnode的定义中并没有key属性。
// body下的
{
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.
M
from blog.
@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.
对着源码看了下,豁然洞开,赞👍
from blog.
请问,patchVnode 中的 updateEle(el, vnode, oldVnode) 的含义是啥,看了下该函数,应该是为节点设置class、ID、data等属性,谢谢
from blog.
mark
from blog.
mark,感谢
from blog.
看完了 一开始看得很头疼 特别是updateChildren 那里
不过慢慢啃下来 有种豁然开朗的感觉
谢谢up~
from blog.
题主对diff算法解析的真是到位,虽然diff不是最优解,但是综合来看,易推广
from blog.
感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
第四步逻辑大概就是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.
这个b不会复用这块,看源码如果没有设置key的话 他也会去遍历oldch找到b然后复用的吧,只是有没有key查找的速度不一样 有key的话是map映射 查找比遍历快
from blog.
感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
第四步逻辑大概就是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的后面~
第四步逻辑大概就是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节点
from blog.
你好,我发现你的第一个例子,也就是 不带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.
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.
感谢@aooy分享,很赞的文章~~
不过这里有个小问题,这张图里的第4步应该没有移动的操作,只是移动了idx, c的位置是在第5步中a, b节点删除掉之后,自然地跟在了d的后面~
第四步逻辑大概就是newCh的newStartIdx, newEndIdx在c元素上发生了重叠,然后在下一次循环中进入了else if(sameVnode(oldEndVnode, newEndVnode))
这个逻辑:patchVnode(oldEndVnode, newEndVnode) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx]不知道我的理解对不~~
大佬 加上key的那张图 c是不是也不需要移动啊
一開始看圖真的看不懂 直接看源碼還清楚些 一直碎碎念為何C要移動
原來家都有這疑惑哈哈
from blog.
你好,我发现你的第一个例子,也就是 不带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.
Mark
from blog.
为什么不设key,newCh和oldCh只会进行头尾两端的相互比较,这样怎么保证更新的dom正确呢?
from blog.
对于vnode.key的比较,会把oldVnode = null
请问这句话怎么理解啊?
from blog.
@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前面。
newStartIdx
和newEndIdx
之间新增的节点为什么要插入在newEndIdx
之后呢?
不应该是插入到它们之间?
仔细想了想 确实应该插入到newEndIdx之后,因为循环结束后 包括newStartIdx和newEndIdx都是新增的节点
from blog.
Related Issues (7)
- aoy的说明书 HOT 6
- 滑动效果的原理及实践一个滑动小插件 HOT 7
- 6种图片灰度转换算法 HOT 2
- 从event loop规范探究javaScript异步及浏览器更新渲染时机 HOT 67
- 关于event loop的task 和microtask的问题 求解惑 HOT 1
- 求个博主联系方式
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog.