背景:
- DOM操作非常耗耗费性能
- 使用jQuery, 可以自行控制DOM操作的时机, 手动调整
- Vue和React是数据驱动视图, 如何有效控制DOM操作?
解决:
- 有一定复杂度减少计算次数(较难)。
- 把计算转化为JS计算(JS执行速度快)。
- vdom - 用JS模拟DOM结构, 计算出最小变更, 再操作DOM
<div id="app" class="container">
<p>title</p>
<ul style="font-size: 20px">
<li>apple</li>
</ul>
</div>
{
tag: 'div',
props: {
className: 'container',
id: 'app'
},
children: [
{
tag: 'p',
children:: 'title',
},
{
tag: 'ul',
props: { style: 'font-size: 20px' },
children: [
{ tag: 'li', children: 'apple' }
]
}
]
}
实现步骤:
- 用JS模拟DOM结构(vnode)。所有的XML语言可以用法JSON表示。class是es6中的关键词, 用className代替。
- 新旧vnode对比,得出最小的更新范围, 最后更新BDOM
-
vnode(snabbdom)
考点: vnode实现html代码片段。
h 函数, h 函数主要根据传进来的参数,返回一个 vnode 对象
- vnode 对象,用来表示相应的 dom 结构
-
虚拟DOM-diff算法概述
diff
算法,算法的基本步骤如下: 用 js 对象来描述 dom 树结构,然后用这个 js 对象来创建一棵真正的 dom 树,插入到文档中
- 当状态更新时,将新的 js 对象和旧的 js 对象进行比较,得到两个对象之间的差异
- 将差异应用到真正的 dom 上
树diff的时间复杂度O(n^3)。遍历tree1、遍历tree2、排序,分别是n。1000个节点, 计算1亿次, 算法不可用。
优化时间复杂到O(n):
- 只比较同一层级, 不跨级比较
- tag不相同, 则直接删除重建, 不再深度比较
- tag和key, 两者相同, 则认为是相同节点, 不再深度比较
snabbdom源码分析
官网的🌰, 先init, 使用h创建vnode,patch(container, vnode)
; 使用h创建newVnode,patch(vnode, newVnode)
生成vnode**
首先会对参数进行格式化,然后对export interface VNode {
sel: string | undefined;
data: VNodeData | undefined;
children: Array<VNode | string> | undefined;
elm: Node | undefined;
text: string | undefined;
key: Key | undefined;
}
export function h(sel: any, b?: any, c?: any): VNode
children
属性做处理,将可能不是vnode
的项转成vnode
,如果是svg
元素,会做一个特殊处理,最后返回一个vnode
对象。
patch函数**patch(oldVnode: VNode | Element, vnode: VNode): VNode
- 如果不是vnode是element, 则转为空vnode
- 对比两个vnode
- 如果sameVnode 相同时 (sel 和 key相同) 调用 patchVnode
- 否则, 更新视图。依据变化key分别进行创建、删除
patchVnode函数**
对传入的两个 vnode
做 diff
,如果存在更新,将其反馈到 dom
上
patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue)
首先调用 vnode
上的 prepatch hook
,如果当前的两个 vnode
完全相同,直接返回。接着调用 module
和 vnode
上的 update hook
。然后会分为以下几种情况做处理:
- 均存在
children
且不相同,调用updateChildren
- 新
vnode
存在children
,旧vnode
不存在children
,如果旧vnode
存在text
先清空,然后调用addVnodes
- 新
vnode
不存在children
,旧vnode
存在children
,调用removeVnodes
移除children
- 均不存在
children
,新vnode
不存在text
,移除旧vnode
的text
- 均存在
text
,更新text
最后调用 postpatch hook
。
updateCildren函数**
updateChildren(parentElm: Node, oldCh: Array<VNode>,
newCh: Array<VNode>, insertedVnodeQueue: VNodeQueue)
整个过程简单来说,对两个数组进行对比,找到相同的部分进行复用,并更新。