虚拟DOM
介绍
虚拟DOM是一个能够表示DOM树的对象,包含标签名,事件,属性,类名,子元素等等。
优势
- 减少DOM操作的次数,可以将多次的DOM操作合并到一次更新
- 减少DOM操作的范围,借助DOM diff把多余的操作去掉,譬如说更新1000个节点中的10个
- 能跨平台渲染
缺点
需要额外的创建函数,如React需要使用createElement创建虚拟DOM,但可以使用JSX(React)或模板语法(Vue)代替,然后通过构建工具进行转换。DOM diff
介绍
当我们拿到了新旧两个虚拟DOM树,自然就需要将这两者之间的变化应用到真实DOM当中,这个过程就是DOM diff大体思路
Vue
Vue:https://juejin.im/post/6844903607913938951
Vue的diff算法策略是一边比较节点一边进行DOM更新,具体函数为patch.js。
而比较策略则是采用同级比较的方法,参照下图:
patch函数接收oldVnode和vnode参数,先判断是否为同一vnode,如果不是,那么直接将oldVnode的DOM元素替换为vnode的DOM元素,如果是,则进行使用patchVnode函数打补丁操作(更新属性、事件、子节点等等)
比较是否为同一vnode的逻辑如下:
function sameVnode (a, b) {return (a.key === b.key && // key值a.tag === b.tag && // 标签名a.isComment === b.isComment && // 是否为注释节点// 是否都定义了data,data包含一些具体信息,例如onclick , styleisDef(a.data) === isDef(b.data) &&sameInputType(a, b) // 当标签是<input>的时候,type必须相同)}
在patchVnode函数中,最重要的是updateChildren函数,此函数进行更新新旧vnode的子节点的操作。
这个函数的更新策略如下:
- 分别对比oldCh和ch头尾节点,存在头头,头尾,尾头,尾尾四种情况,如果命中则进行patchVnode操作
- 针对尾头情况,则将旧的DOM元素移动到头部
- 针对头尾情况,则将新的DOM元素移动到尾部
- 如果上述四种情况都不存在,那么就查找oldCh当中与当前vnode相同的oldVnode,策略:检查vnode是否存在key值,如果存在key值,则通过查oldCh的哈希表(key -> index)查询是否存在此key,如果不存在key值,那么就遍历oldCh查询第一个相同的vnode
- 如果不存在一个相同的vnode,那么就直接创建新的元素插入到父元素中
- 如果存在相同vnode,那么就patchVnode
- 最后就检查是否存在没插入的元素或者多余的还没被删除的元素,进行统一处理
React
React:https://zhuanlan.zhihu.com/p/20346379
在React中,diff策略(旧版,新版为Fiber)可以分为三个部分:
- tree diff
- component diff
- element diff
tree diff
与Vue相似,React会对新旧两棵树进行逐层比较,如果是组件节点,则走component diff流程,如果是元素节点,则走element diff流程component diff
判断是否为同一类型组件,如果不同则直接替换,如果相同则更新属性事件等,然后继续进行tree diff操作element diff
如果是原生标签,则看标签名,标签不同则替换,相同则更新属性等,然后继续进行tree diff
与Vue类似,在更新子元素的时候,React当中会根据每个元素的key值来判断是否为同一个元素,以此来实现对旧元素的复用。
缺陷
DOM diff算法面对大量相同类型的节点变更时,会无法有效地复用已有的节点,譬如下面这个例子:
向列表中插入一个元素F,理想的状态是直接复用节点,然后插入新元素,但实际上是这样的:
由于都是同类型的元素,DOM diff无法精准识别出当前节点跟哪个旧节点相同,只会模糊地认为当前元素标签名与自身一致,就认为是同一节点,直接进行更新。
为了解决这种情况,Vue和React都引入了key值来标识节点,使算法能根据key值精准识别节点,从而使得就有节点能合理复用。
