关于vue的虚拟dom和diff算法

vnode

首先要说明虚拟节点,本质就是一个对象:

{
sel: 'div', // 选择器
elm: undefined, // 关联的dom对象
key: undefined,
text: '', // 标签文本
data: {},
children: undefined // 子节点
}

vnodeh函数产生,模板引擎会最终把标签转换成h函数表达式,即h(sel,data,c)的形式,最终得到的就是vnode对象。在渲染的过程中,vnode会作为patch函数的参数,patch函数负责真正的渲染工作,即进行diff算法并且操作dom。patch函数是vue引擎调用的,具体时机是在数据劫持之后。

diff算法

diff算法是发生在vnode上的,即是在生成dom节点插入dom树之前的行为,比较的是vnode。

diff算法确认相同vnode的规则:选择器(sel)相同且key相同。

关于“列表”中增删改元素:如果我们没有给元素设置key属性,则判断vnode只能依据sel(可以说就是标签名),相当于不管你怎么主观移动列表元素,diff算法只会把新旧vnode按列表顺序逐一比较,不一样就改,最终多了就删,少了就在后面append,所以现象就是总会在后面追加或删除dom,然后从头把旧列表对照新列表重新改一遍,当然对比vnode一样时候的不会动旧dom。

patch函数流程

patch执行函数需要2个参数,旧vnode和新vnode。

这里有个特殊情况,首先通过是否有sel属性判断传入的旧vnode是否是原生dom对象,如果是原生dom需要先把他包装成一个vnode,然后才是diff算法处理dom的过程。

diff算法完整过程

判定新旧vnode不同:

patch会直接根据新vnode创建dom并insertBefore到旧dom上,然后删除旧vnode对应的dom,子dom会被连带脱离dom树。

判定新旧vnode相同:

[1] 首先判断新旧dom是不是同一个对象,是就什么都不用做(省去多余dom操作)

(以新vnode为基准分条件判断:)
[2.1] 如果新vnode内部有文本,并且跟旧vnode相同,也什么都不做。(省去多余dom操作)
[2.2] 如果新vnode内部有文本,并且跟旧vnode不同,或者旧vnode就没有(内部是子vnode),直接innerText插值

[3.1] 如果新vnode内部没有文本,即有子vnode,需要先看旧vnode有没有文本,有就先删除旧dom中文本然后再appendChild子节点(因为appendChild并不会替换掉文本)
[3.2] 如果新vnode内部没有文本,即有子vnode,并且旧vnode没有文本,

如果旧vnode子节点是空数组或者undefined,则把根据新vnode的children新建dom并appendChild进去;

如果旧vnode有子节点,开始同层逐一比较

diff算法最复杂的部分在这里,即children都有内容的时候判断更新,这里使用的diff策略其实用的是经典的内容对比算法(跟git中的新旧对比一样),具体要分4种情况:新增节点、删除节点、上移节点、下移节点

这个算法巧妙的地方是利用了4个指针:新前、新后、旧前、旧后
新前、新后指的是新vnode的children列表开头和结尾的指针,前指针只会往后移动,后指针只会往前移动
旧前、旧后指的是旧vnode的children列表开头和结尾的指针,前指针只会往后移动,后指针只会往前移动

整个循环以新指针为基准开始,根据规则循环移动指针,循环终止条件是新前不能大于新后 && 旧前不能大于旧后
每次循环会根据优先级规则判断是否命中,命中则移动指针并进入下次循环,未命中会根据下一优先级规则判断命中:新前与旧前>新后与旧后>新后与旧前>新前与旧后

规则1-新前与旧前: 判断新前vnode是否与旧前vnode相同,相同则新前旧前指针后移,当次循环结束;否则进入规则2。
最终循环结束的时候:如果如果新前与新后率先汇合,则旧前与旧后之间的vnode需要删除,对应dom应该被删除;相反新前与新后之间的vnode需要被插入。

规则2-新后与旧后:判断新后vnode是否与旧后vnode相同,相同则新前旧前指针前移,当次循环结束;否则进入规则3。
最终循环结束的时候:如果新后与新前率先汇合,则旧后与旧前之间的vnode需要“删除”,对应dom应该被删除;相反新后与新前之间的vnode应该被插入。

规则3-新后与旧前:判断新后vnode是否与旧前vnode相同,相同则把旧前标记为undefined避免错误dom操作,并把旧前对应dom移动到旧后之后,当次循环结束;否则新后前移,旧前后移,进入规则4。

规则4-新前与旧后:判断新前vnode是否与旧后vnode相同,相同则把旧后标记为undefined避免错误dom操作,并把旧后对应dom移动到旧前之前,当次循环结束;否则进入规则1。

这个过程可以理解为一个收拢的过程,收拢的过程中,过滤掉相同的,同时整理内部的顺序方便下一次收拢。
这么一轮循环之后,该调整的调整,该删除的删除,该新增的新增,安排的明明白白。

由此可见,vue的patch只能“同层”比较,比较的是同层的单个新旧vnode,新旧“不同”就暴力insertBefore新dom然后删除旧dom;新旧“相同”并且都有children的时候会使用经典diff算法优化dom操作。比的是“同层”vnode,最多会连带下一级children。

同时还要注意,vue的dom操作是在diff算法过程中的,并且创建dom是一个根据vnode递归的创建过程,子dom节点插入是appendChild的方式。

snabbdom

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容