好久没有来博客了,疫情期间3月中旬从杭州实习公司辞职,在杭州的出租房呆了两个月,准备毕设和毕业相关的材料,顺便找新的工作。最终还是留在了魔都这座城市。现在也算稳定下来了,准备以后好好维护一下博客。第一篇文章就以虚拟DOM作为起点吧。
虚拟dom原理
1、为什么要使用虚拟dom
当浏览器内核接接收到HTML文件之后做了那些事情?
1、解析HTMl元素,生成对应的DOM树。
2、解析CSS文件,生成页面css规则树(Style Rules)。
3、将CSS树和DOM相关联起来,生成Render树。
4、布局(layout/reflow:重排),浏览器会为Render树上的每个节点确定在屏幕上的尺寸、位置。
5、paint:绘制Render树,绘制页面像素信息到屏幕上当我们使用原生js去操作DOM节点的时候,浏览器会去把整个过程重新走一遍,耗时耗力。
虚拟DOM的作用就是使用javascript对象表示virtual node(VNode),
根据VNode计算出真实DOM需要做的最小变动,然后再操作真实DOM节点,提高渲染效率。
跨平台:Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9P4zcopy-1600676864444)(https://segmentfault.com/img/bVLU2p?w=623&h=396)]
2、原理主要流程
-
用JavaScript模拟DOM树,并渲染这个DOM树
-
比较新老DOM树,得到比较的差异对象
-
把差异对象应用到渲染的DOM树。
3、如何用 JavaScript 对象来表现一个 DOM 元素的结构
<div class='box' id='content'><div class='title'>Hello</div><button>Click</button>
</div>对应:{tag: 'div',attrs: { className: 'box', id: 'content'},children: [{tag: 'div',arrts: { className: 'title' },children: ['Hello']},{tag: 'button',attrs: null,children: ['Click']}]
}
4、diff算法(先序深度优先)
- 遍历算法
<div><div><p>I am P</p></div><ul><li>#list1</li><li><span>#list2</span></li></ul>
</div>
- 先序深度优先(根左右):
结果:ABDCEFG - 中序深度优先(左根右):
结果:BDAECFG - 后序深度优先(左右根):
结果:DBEGFCA
-
diff算法
在标准dom机制下:在同一位置对比前后的dom节点,发现节点改变了,会继续比较该节点的子节点,一层层对比,找到不同的节点,然后更新节点。
在react的diff算法下,在同一位置对比前后dom节点,只要发现不同,就会删除操作前的dom节点(包括其子节点),替换为操作后的dom节点。
传统diff算法(时间复杂度 O(n^3)):
比如左侧树a节点依次进行如下对比,左侧树节点b、c、d、e亦是与右侧树每个节点对比
算法复杂度能达到O(n^2),n代表节点的个数a->e、a->d、a->b、a->c、a->a查找完差异后还需计算最小转换方式,最终达到的算法复杂度是O(n^3)[link](https://www.zhihu.com/question/66851503/answer/246766239)
优化后的diff算法(时间复杂度 O(n)):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BrtxRM4m-1600676864451)(https://segmentfault.com/img/bVSLSc?w=576&h=318)]
在同一位置对比前后dom节点,只要发现不同,就会删除操作前的domm节点(包括其子节点),替换为操作后的dom节点。diff策略:策略一、Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。(tree diff)策略二、拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。(component diff)策略三、对于同意层级的一组子节点,他们可以通过唯一id进行区分。(element diff)
tree diff:
- 只会比较同一层级的DOM节点
- 当发现当前节点不存在时,移除当前节点及其子节点
comonent diff:
- 如果是同类型的组件,则按照策略继续向下比较
(对于同一类型的组件,有可能它的virtual Dom没有发生变化,如果可已确切知道没有变化,可无需进行diff算法分析:shouldComponentUpdate(nextProps, nextState)) - 如果是不同类型的组件,直接替换整个组件下的所有子节点
element diff:
1、首先遍历新集合,通过唯一的key来判断新旧集合中是否存在相同的节点,若存在则通过顺序优化手段进行移动操作,若不存在则新增。
2、当完成新集合中所有节点差异化对比后,再次遍历旧集合,判断是否有新集合中不存在但旧集合中存在的点,进行删除操作。
)
5、patch
经过上面的diff算法,最终我们会得到对应的差异对象
差异对象结构:{type,vdom,props: [{key,type,value}],children}
最外层的type对应四种操作:新建、删除、替换、更新:
const nodePatchType = {CREATE: 'create node',REMOVE: 'remove node',REPLACE: 'replace node',UPDATE: 'update node'
}
props type对应两种操作:更新删除
const propPatchTypes = {REMOVE: 'remove prop',UPDATE: 'update prop'
}
-
创建新增节点
-
删除已废弃的节点
-
修改需要更新的节点。
代码参考地址:https://www.php.cn/js-tutorial-411406.html
Key的作用list-diff源码:https://www.npmjs.com/package/list-diff2 或 https://blog.csdn.net/yang00322/article/details/77943548?utm_source=blogxgwz5