Vdom
- vdom 是实现 vue 和 React 的基石 ,通过 JS 模拟 DOM 结构(vnode)
- 虚拟dom 指将页面上的所有 dom 元素以对象的形式保存于内存中
- 包含了标签名、属性、子元素等信息;
- 通过新旧 vnode 对比,得出最小的更新范围,最终对需要更新的 dom 进行实际操作;
模拟 Vdom 结构:
<div id="div1" class="container">
<p>title</p>
<ul style="font-size: 20px">
<li>hello</li>
</ul>
</div>
const vdom = {
tag: 'div',
props: {
className: 'container',
id: 'div1'
},
children: [
{
tag: 'p',
children: 'title' // 文本内容
},
{
tag: 'ul',
props: {
style: 'font-size:20px'
},
children: [
{
tag: 'li',
children: 'hello' // 文本内容
}
]
}
]
}
使用 snabbdom 了解 vdom 原理:
- html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script src="./snabbdom.js"></script>
</body>
</html>
- snabbdom.js 文件: 定义 h 函数,最终返回
vnode
,vnode
本身也是函数,返回一个对象即 JS 模拟的 DOM 结构
const h = window.snabbdom.h
const container = document.getElementById('container') // 挂载 Vdom 的容器
h 函数 和 vnode 函数内部
function h() {
// ... do something
return vnode(sel, data, children, text, undefined)
}
function vnode(sel, data, children, text, elm, key) {
let key = data === undefined ? undefined : data.key
return { sel, data, children, text, elm, key }
}
- vnode:
sel
为tag
标签,如 div p a spandata
- 属性 attr、children
- 子节点elm
- vnode 对应 DOM 真实元素text
- 文本key
用于 diff 算法对比新旧 dom 实现更新和重新渲染
let vnode = h(
'ul#list', // 标签ul id=list
{}, // 属性
[
h(
'li.item', // li标签 class = item
{},
'Item 1' // 内容(text)
),
h('li.item', {}, 'Item 2')
]
)
- snabbdom.init : init 会初始化生命周期,初始化完成后会返回
patch
函数 - patch: 函数执行结果会将
vdom
渲染成真实 dom- 传入的第一个参数是容器会创建一个空的 vnode,并关联 elm(真实DOM元素)
- 传入的参数都是 vNode,则会进行 diff 算法比较
const patch = snabbdom.init([
snabbdom_class, // 提供了对元素类名操作的功能,包括添加、删除和切换类名
snabbdom_props, // 用于处理元素的属性,包括属性的设置、获取和删除。
snabbdom_style, // 用于处理元素的样式,包括样式的设置和获取。
snabbdom_eventlisteners // 用于处理元素的事件监听器,包括添加和删除事件监听器,以及触发事件。
])
patch(container, vnode) // 初次渲染:虚拟DOM渲染到HTML
// 模拟更新
document.getElementById('btn').addEventListener('click', () => {
const newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 3'),
h('li.item', {}, 'Item 2')
])
patch(vnode, newVnode) // 新的虚拟的DOM
vnode = newVnode // patch 之后,应该用新的覆盖现有的 vnode
})
diff
- 最开始会遍历 tree1 和 tree 2 继续比较,然后进行 排序,导致时间复杂度到达 O^3;
- 后来进行了优化将时间复杂度优化到了O(n)
- 只比较同一层级,如果
tag
不相同直接删除重建 - 如果
tag
和key
都相同不再深度比较,而是直接进行一个新旧vnode
的比较
- 只比较同一层级,如果
patch
函数中新旧vNode
对比的大致原理:- 首先将新的
vnode
关联到旧的vnode
所对应的 elm(真实Dom) - 然后进行
children
和text
的对比,分为4个方向 - 如果
oldVnode
含有children
,newVnode
为text
:移除旧vnode
,设置新text
- 如果
oldVnode
为text
,newVnode
含有children
:创建新vnode
,移除旧text
- 如果
oldVnode
为text
,newVnode
也为text
:移除旧的text
设置新text
- 如果
oldVnode
含有children
,newVnode
也含有children
:每一个框架的算法都不同,共性都依赖于key
进行计算 - 旧的开始和旧的开始,旧的结束和旧的结束,旧的开始和新的结束,旧的结束和新的开始4个维度进行计算
- 首先将新的