背景:
- 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)
整个过程简单来说,对两个数组进行对比,找到相同的部分进行复用,并更新。
