virtual DOM和真实DOM的区别_【React】529 手写虚拟DOM和DOMdiff

b807bb50f51f15d6452d2878172f2286.png

作者:chenhongdong
链接:https://juejin.im/post/5c8e5e4951882545c109ae9c

时至今日,前端对于知识的考量是越来越有水平了,逼格高大上了,各类框架大家已经可以说无论是工作还是日常中都已经或多或少的使用过了,曾经听说很多人被问到过虚拟DOM和DOM-diff算法是如何实现的,有没有研究过?

想必问出此问题的也是高手高手之高高手了,很多人都半开玩笑的说:“面试造航母,工作拧螺丝”

那么,话不多说了,今天就让我们也来一起研究研究这个东东

好饭不怕晚,沉淀下来收收心!我们虽然走的慢,但是却从未停下脚步

神奇的虚拟DOM

首先神奇不神奇的我们先不去关注,先来简单说说何为虚拟DOM

虚拟DOM简而言之就是,用JS去按照DOM结构来实现的树形结构对象,你也可以叫做DOM对象

好了,一句话就把这么伟大的东西给解释了,那么不再耽误时间了,赶紧进入主环节吧

当然,这里还有整个项目的地址方便查看

实现一下虚拟DOM

在亲自上阵之前,我们让粮草先行,先发个图,来看一下整个目录结构是什么样子的

7af75fe2dcd61eb7e1d7e23bbd923c83.png

这个目录结构是用create-react-app脚手架直接生成的,也是为了方便编译调试

// 全局安装

现在我们开始正式写吧,从创建虚拟DOM及渲染DOM起步吧

创建虚拟DOM

在element.js文件中要实现如何创建虚拟DOM以及将创建出来的虚拟DOM渲染成真实的DOM

首先实现一下如何创建虚拟DOM,看代码:

// element.js

写好了方法,我们就从index.js文件入手来看看是否成功吧

调用createElement方法

在主入口文件里,我们主要做的操作就是来创建一个DOM对象,渲染DOM以及通过diff后去打补丁更新DOM,不啰嗦了,直接看代码:

// index.js

createElement方法也是vue和react用来创建虚拟DOM的方法,我们也叫这个名字,方便记忆。接收三个参数,分别是typepropschildren

  • 参数分析

    • type: 指定元素的标签类型,如'li', 'div', 'a'等

    • props: 表示指定元素身上的属性,如class, style, 自定义属性等

    • children: 表示指定元素是否有子节点,参数以数组的形式传入

下面来看一下打印出来的虚拟DOM,如下图

80f7e2c2b90ba4220f1ec4fef089faf3.png

到目前为止,已经轻而易举的实现了创建虚拟DOM。那么,接下来进行下一步,将其渲染为真实的DOM,别犹豫,继续回到element.js文件中

渲染虚拟DOM
// element.js

既然写完了,那就赶快来看看成果吧

调用render方法

再次回到index.js文件中,修改为如下代码

// index.js

通过调用render方法转为真实DOM,并调用renderDom方法直接将DOM添加到了页面内

下图为打印后的结果:

cb08d9faa8570b4e50828c5b2a883370.png

截止目前,我们已经实现了虚拟DOM并进行了渲染真实DOM到页面中。那么接下来我们就有请DOM-diff隆重登场,来看一下这大有来头的diff算法是如何发光发热的吧!

DOM-diff闪亮登场

说到DOM-diff那一定要清楚其存在的意义,给定任意两棵树,采用先序深度优先遍历的算法找到最少的转换步骤

DOM-diff比较两个虚拟DOM的区别,也就是在比较两个对象的区别。

作用: 根据两个虚拟对象创建出补丁,描述改变的内容,将这个补丁用来更新DOM

已经了解到DOM-diff是干嘛的了,那就没什么好说的了,继续往下写吧

// diff.js

代码虽然又臭又长,但是这些代码就让我们实现了diff算法了,所以大家先不要盲动,不要盲动,且听风吟,让我一一道来

比较规则

  • 新的DOM节点不存在{type: 'REMOVE', index}

  • 文本的变化{type: 'TEXT', text: 1}

  • 当节点类型相同时,去看一下属性是否相同,产生一个属性的补丁包{type: 'ATTR', attr: {class: 'list-group'}}

  • 节点类型不相同,直接采用替换模式{type: 'REPLACE', newNode}

根据这些规则,我们再来看一下diff代码中的walk方法这位关键先生

walk方法都做了什么?

  • 每个元素都有一个补丁,所以需要创建一个放当前补丁的数组

  • 如果没有new节点的话,就直接将type为REMOVE的类型放到当前补丁里

if (!newNode) {
  • 如果新老节点是文本的话,判断一下文本是否一致,再指定类型TEXT并把新节点放到当前补丁

else 
  • 如果新老节点的类型相同,那么就来比较一下他们的属性props

    • diffChildren

    • 遍历oldChildren,然后递归调用walk再通过child和newChildren[index]去diff

    • diffAttr

    • 去比较新老Attr是否相同

    • 把newAttr的键值对赋给patch对象上并返回此对象

    • 属性比较

    • 然后如果有子节点的话就再比较一下子节点的不同,再调一次walk

else 
  • 上面三个如果都没有发生的话,那就表示节点单纯的被替换了,type为REPLACE,直接用newNode替换即可

else {
  • 当前补丁里确实有值的情况,就将对应的补丁放进大补丁包里

if (current.length > 

以上就是关于diff算法的分析过程了,没太明白的话没关系,再反复看几遍试试,意外总是不期而遇的

diff已经完事了,那么最后一步就是大家所熟知的打补丁

补丁要怎么打?那么让久违的patch出来吧

patch补丁更新

打补丁需要传入两个参数,一个是要打补丁的元素,另一个就是所要打的补丁了,那么直接看代码

import { Element, render, setAttr } 

看完代码还需要再来简单的分析一下

patch做了什么?

  • 用一个变量来得到传递过来的所有补丁allPatches

  • patch方法接收两个参数(node, patches)

    • 在方法内部调用walk方法,给某个元素打上补丁

  • walk方法里获取所有的子节点

    • 给子节点也进行先序深度优先遍历,递归walk

    • 如果当前的补丁是存在的,那么就对其打补丁(doPatch)

  • doPatch打补丁方法会根据传递的patches进行遍历

    • 判断补丁的类型来进行不同的操作

  1. 属性ATTR for in去遍历attrs对象,当前的key值如果存在,就直接设置属性setAttr;如果不存在对应的key值那就直接删除这个key键的属性

  2. 文字TEXT 直接将补丁的text赋值给node节点的textContent即可

  3. 替换REPLACE 新节点替换老节点,需要先判断新节点是不是Element的实例,是的话调用render方法渲染新节点;

    不是的话就表明新节点是个文本节点,直接创建一个文本节点就OK了。

    之后再通过调用父级parentNode的replaceChild方法替换为新的节点

  4. 删除REMOVE 直接调用父级的removeChild方法删除该节点

将patch方法默认导出方便调用

好了,一切都安静下来了。让我们回归index.js文件中,去调用一下diff和patch这两个重要方法,看看奇迹会不会发生吧

回归

// index.js

将修改后的代码保存,会在浏览器里看到DOM被更新了,如下图

9324bfde141907fd3b71b2ea3bcfde68.png

到这里就finish了,内容有些多,可能不是很好的消耗,不过没关系,就让我用最后几句话来总结一下实现的整个过程吧

四句话

我们来梳理一下整个DOM-diff的过程:

  1. 用JS对象模拟DOM(虚拟DOM)

  2. 把此虚拟DOM转成真实DOM并插入页面中(render)

  3. 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)

  4. 把差异对象应用到真正的DOM树上(patch)

行了,就这四句话吧,说多了就有点画蛇添足了。好久没有写文章了,很感谢小伙伴们的观看,辛苦各位了,886

▼原创系列推荐▼1. JavaScript 重温系列(22篇全)2. ECMAScript 重温系列(10篇全)3. JavaScript设计模式 重温系列(9篇全) 4. 正则 / 框架 / 算法等 重温系列(16篇全) 5. Webpack4 入门(上) || Webpack4 入门(下) 6. MobX 入门(上)  ||  MobX 入门(下) 7. 59篇原创系列汇总

aec31fcc127ea1b8637618c1b16d57fc.png

回复“加群”与大佬们一起交流学习~

7a8427af704ac17f988b7012e5013daf.png 点这,与大家一起分享本文吧~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值