参考
真实DOM的渲染
在讲虚拟DOM之前,先说一下真实DOM的渲染。
浏览器真实DOM渲染的过程大概分为以下几个部分:
-
构建DOM树。通过html parser解析处理html标记,将它们构建为DOM树(DOM tree),当解析器遇到非阻塞资源(图片,css),会继续解析,但是如果遇到script标签(特别是没有async 和 defer属性),会阻塞渲染并停止html的解析,这就是为啥最好把script标签放在body下面的原因。
-
构建cssOM树。与构建DOM类似,浏览器也会将样式规则,构建成CSSOM。浏览器会遍历CSS中的规则集,根据css选择器创建具有父子,兄弟等关系的节点树。
-
构建Render树。这一步将DOM和CSSOM关联,确定每个 DOM 元素应该应用什么 CSS 规则。将所有相关样式匹配到DOM树中的每个可见节点,并根据CSS级联确定每个节点的计算样式,不可见节点(head,属性包括 display:none的节点)不会生成到Render树中。
-
布局/回流(Layout/Reflow)。浏览器第一次确定节点的位置以及大小叫布局,如果后续节点位置以及大小发生变化,这一步触发布局调整,也就是 回流。
-
绘制/重绘(Paint/Repaint)。将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。如果文本、颜色、边框、阴影等这些元素发生变化时,会触发重绘(Repaint)。为了确保重绘的速度比初始绘制的速度更快,屏幕上的绘图通常被分解成数层。将内容提升到GPU层(可以通过tranform,filter,will-change,opacity触发)可以提高绘制以及重绘的性能。
-
合成(Compositing)。这一步将绘制过程中的分层合并,确保它们以正确的顺序绘制到屏幕上显示正确的内容。
虚拟DOM
虚拟DOM本质上是一个js对象,通过对象来表示真实的DOM结构。tag用来描述标签,props用来描述属性,children用来表示嵌套的层级关系。
const vnode = { tag: 'div', props: { id: 'container', }, children: [{ tag: 'div', props: { class: 'content', }, text: 'This is a container' }] } //对应的真实DOM结构 <div id="container"> <div class="content"> This is a container </div> </div>
虚拟DOM的更新不会立即操作DOM,而是会通过diff算法,找出需要更新的节点,按需更新,并将更新的内容保存为一个js对象,更新完成后再挂载到真实dom上,实现真实的dom更新。
虚拟DOM优点(上面引出的)(这就是为啥需要虚拟DOM)
总结起来,虚拟DOM的优势有以下几点:
-
小修改无需频繁更新DOM,框架的diff算法会自动比较,分析出需要更新的节点,按需更新
-
更新数据不会造成频繁的回流与重绘
-
表达力更强,数据更新更加方便
-
保存的是js对象,具备跨平台能力
不足
虚拟DOM同样也有缺点,首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
虚拟DOM实现原理
主要分三部分:
-
通过js建立节点描述对象
-
diff算法比较分析新旧两个虚拟DOM差异
-
将差异patch到真实dom上实现更新
Diff算法
为了避免不必要的渲染,按需更新,虚拟DOM会采用Diff算法进行虚拟DOM节点比较,比较节点差异,从而确定需要更新的节点,再进行渲染。vue采用的是深度优先,同层比较的策略。
新节点与旧节点的比较主要是围绕三件事来达到渲染目的
-
创建新节点
-
删除废节点
-
更新已有节点
patch 函数代码:
//patch函数 oldVnode:老节点 vnode:新节点 function patch (oldVnode, vnode) {undefined ... if (sameVnode(oldVnode, vnode)) {undefined patchVnode(oldVnode, vnode) //如果新老节点是同一节点,那么进一步通过patchVnode来比较子节点 } else {undefined /* -----否则新节点直接替换老节点----- */ const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点 let parentEle = api.parentNode(oEl) // 父元素 createEle(vnode) // 根据Vnode生成新元素 if (parentEle !== null) {undefined api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素 api.removeChild(parentEle, oldVnode.el) // 移除以前的旧元素节点 oldVnode = null } } ... return vnode } //判断两节点是否为同一节点 function sameVnode (a, b) {undefined return ( a.key === b.key && // key值 a.tag === b.tag && // 标签名 a.isComment === b.isComment && // 是否为注释节点 // 是否都定义了data,data包含一些具体信息,例如onclick , style isDef(a.data) === isDef(b.data) && sameInputType(a, b) // 当标签是<input>的时候,type必须相同 ) }
标签:oldVnode,DOM,更新,vnode,算法,虚拟,Diff,节点 From: https://www.cnblogs.com/the-big-dipper/p/16596278.html