什么是虚拟DOM
简单地说虚拟DOM其实就是使用JavaScript对象的形式来一个DOM节点,包含了type、props、children三个属性:
<div class="container">
hello world
</div>
将👆上面的HTML代码以虚拟DOM的形式表示:
{
type: 'div',
props: {
className: 'container'
},
children: [
"hello world"
]
}
因为DOM树🌲是一个树形的结构,所以使用JavaScript对象就可以表示出树的结果。HTML和虚拟DOM有点类似于XML和JSON,使用不同的形式来表示相同的数据。
虚拟DOM的好处
需要注意的是虚拟DOM不一定比真实的DOM操作快,JavaScript引擎和DOM引擎使用的是同一个主线程,任何涉及到DOM的操作都需要先把JavaScript的数据结构转换为DOM的数据结构,再将JavaScript引擎挂起执行DOM引擎,执行完成后再切换执行JavaScript引擎,这种上下文的切换是很消耗性能的,所以解决DOM操作的性能问题的关键在于减少不必要的DOM操作。
那么虚拟DOM没有带来任何的性能的优化吗?也不是这样!虚拟DOM能够实现最细粒度的更新你的DOM,对于DOM操作我更新DOM的常见做法是使用innerHTML,但是innerHTML的JavaScript计算和DOM操作通常和你的界面数据大小挂钩,即innerHTML的时间复杂度O = JavaScript操作时间 + 重新创建所有DOM元素的时间;而虚拟DOM更新UI界面的时间复杂度O = 渲染虚拟DOM + diff + 必要的DOM更新,渲染虚拟DOM和diff操作都是JavaScript计算不会涉及到JavaScript引擎和DOM引擎的上下文切换。所以虚拟DOM不管每次的数据变化是怎样的,每次重绘的对于DOM的操作都是最小的。
虚拟DOM最大的好处在于抽象了渲染的过程,为应用带来了跨平台的能力,不再是仅仅局限于浏览器端。比如React-Native和WeeX可以运行在Android、IOS平台上。
真实DOM到虚拟DOM的映射
借助@babel/plugin-transform-react-jsx可以实现从真实DOM到虚拟DOM的转换。
1.安装babel依赖:npm i -D @babel/cli @babel/core @babel/plugin-transform-react-jsx
2.配置 .babelrc:
// .babelrc文件
{
"plugins": [
["transform-react-jsx", {
"pragma": "h"
}]
]
}
下面👇就可以借助babel来将真实DOM转换为虚拟DOM
新建index.js文件📃:
function toVDOM() {
return (
<ul className="group">
<li className="item">吃饭</li>
<li className="item">睡觉</li>
<li className="item">打代码</li>
</ul>
)
}
执行命令npx babel main.jsx --out-file vdom.js后得到:
function toVDOM() {
return h("ul", {
className: "group"
}, h("li", {
className: "item"
}, "吃饭"), h("li", {
className: "item"
}, "睡觉"), h("li", {
className: "item"
}, "打代码"));
}
所以我们只需要实现h函数就能得到虚拟DOM了。
function h(type, props, ...children) {
return {
type,
props: props || {},
children: children.flat()
}
}
render函数
上面我们已经实现了真实DOM到虚拟DOM的转换,接下来我们将实现render函数将虚拟DOM渲染成真实DOM:
function render(element, container) {
const { type, props = {}, children = [] } = element;
const dom = typeof element === 'number' || typeof element === 'string' ? document.createTextNode(element) : document.createElement(type);
Object.keys(props).forEach(p => {
dom[p] = props[p]
});
children.forEach(c => {
render(c, dom)
});
container.appendChild(dom);
}
将通过h函数生成的虚拟DOM传入到render函数中就能实现将虚拟DOM渲染成真实DOM了:
render(element, document.getElementById('app'))
接下来的更新写作内容
- 虚拟
DOM的更新