- vdom是vue和React的核心,先讲哪个都绕不开它
- vdom比较独立,使用也比较简单
- 如果面试闻到vue和React和实现,免不了问vdom
vdom是什么?为何会存在vdom?
- virtual dom,虚拟DOM
- 用JS模拟DOM结构
- 将DOM对比操作放在JS层来做,提高效率
- 提高重绘性能
为何会存在:
- DOM操作是“昂贵”的,非常耗费性能,js运行效率高
- 尽量减少DOM操作,而不是“推倒重来”
- 项目越复杂,影响就越严重
- vdom即可解决这个问题
vdom
- 有了一定复杂度,想减少计算次数比较难
- 能不能把计算,更多的转移为JS计算?因为JS执行速度很快
- vdom - 用JS模拟DOM结构
- 新旧vnode对比,得出最小的更新范围,最后更新DOM
用js模拟dom结构
<div id="div1" class="container">
<p>vdom></p>
<ul style="font-size: 20px;">
<li>a</li>
</ul>
</div>
{
tag: 'div',
props: {
className: 'container',
id: 'div1'
},
children: [
{
tag: 'p',
children: 'vdom'
},
{
tag: 'ul',
props: {
style: 'font-size: 20px'
},
children: [
{
tag: 'li',
children: 'a'
}
]
}
]
}
- Vue3.0重写了vdom的代码,优化了性能
- 但vdom的基本理念不变
vdom如何应用,核心API是什么
使用snabbdom
<body>
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
<script>
var snabbdom = window.snabbdom
// 定义patch
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
// 定义h
var h = snabbdom.h
var container = document.getElementById('container')
// 生成vnode
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2')
])
patch(container, vnode)
document.getElementById('btn-change').addEventListener('click', function(){
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item B'),
h('li.item', {}, 'Item 3')
])
patch(vnode, newVnode)
})
</script>
</body>
介绍一下diff算法
- 什么是diff算法?
(1)diff即对比,是一个广泛的概念,如linux diff命令、git diff等
(2)两个js对象也可以做diff,如 https://github.com/cujojs/jiff
(3)两棵树做diff,如这里的vdom diff
(4)树diff的时间复杂度为O(n^3)
a. 第一,遍历tree1;第二,遍历tree2
b. 第三,排序
c. 如果树中有1000个节点,要计算1亿次,算法不可用
(5)优化时间复杂度到O(n)
a. 只比较同一层级,不跨级比较
b. tag不相同,则直接删掉重建,不再深度比较
c. tag和key,两者都相同,则认为是相同节点,不再深度比较 - vdom为何用diff算法?
(1)DOM操作是“昂贵”的,因此尽量减少DOM操作
(2)找出本次DOM必须更新的节点来更新,其他的不更新
(3)这个“找出”的过程,就需要diff算法
(4)diff算法是vdom中最核心、最关键的部分
a. patchVnode
b. addVnodes、removeVnodes
c. updateChildren(key的重要性) - diff算法实现流程
(1)patch(container, vnode)
(2)patch(vnode, newVnode)
// patch(container, vnode)情况
function createElement(vnode){
var tag = vnode.tag
var attrs = vnode.attrs || {}
var children = vnode.children || []
if(!tag){
return null
}
// 创建元素
var elem = document.createElement(tag)
// 属性
var attrName
for(attrName in attrs){
if(attrs.hasOwnProperty(attrName)){
// 给elem添加属性
elem.setAttribute(attrName, attrs[attrName])
}
}
children.forEach(childVnode => {
// 给elem田间子元素
elem.appendChild(createElement(childVnode)) // 递归
})
// 返回真实的DOM元素
return elem
}
// 数据
{
tag: 'ul'
attrs: {id: 'list'},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
}, {
tag: 'li',
attrs: {className: 'item'},
children: ['Item 2']
}
]
}
// patch(vnode, newVnode)情况
function updateChildren(vnode, newVnode){
var children = vnode.children || []
var newChildren = newVnode.children || []
// 遍历现有的children
children.forEach(function(child, index){
var newChild = newXChildren[index]
if(newChild == null){
return
}
if(child.tag === newChild.tag){
// 两者tag一样
updateChildren(child, newChild)
} else {
replaceNode(child, newChild)
}
})
}
核心API:
- h函数
- vnode
- patch函数
- diff
- key
vdom存在的价值:
数据驱动视图,控制DOM操作