一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
是什么?
虚拟DOM本质上是一个js对象,通过对象来表示真实的DOM结构。tag用来描述标签,props用来描述属性,children用来表示嵌套的层级关系。
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello world!!!'
]
}
]
}
该对象就是我们常说的虚拟 DOM 了,因为 DOM 是树形结构,所以使用 JavaScript 对象就能很简单的表示。而原生 DOM 因为浏览器厂商需要实现众多的规范(各种 HTML5 属性、DOM事件),即使创建一个空的 div 也要付出昂贵的代价。虚拟 DOM 提升性能的点在于 DOM 发生变化的时候,通过 diff 算法比对 JavaScript 原生对象,计算出需要变更的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图。
渲染虚拟 DOM
虽然虚拟 DOM 可以渲染到多个平台,但是这里讲一下在浏览器环境下如何渲染虚拟 DOM。
function render(vdom) {
// 如果是字符串或者数字,创建一个文本节点
if (typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom)
}
const { tag, props, children } = vdom
// 创建真实DOM
const element = document.createElement(tag)
// 设置属性
setProps(element, props)
// 遍历子节点,并获取创建真实DOM,插入到当前节点
children
.map(render)
.forEach(element.appendChild.bind(element))
// 虚拟 DOM 中缓存真实 DOM 节点
vdom.dom = element
// 返回 DOM 节点
return element
}
function setProps (element, props) {
Object.entries(props).forEach(([key, value]) => {
setProp(element, key, value)
})
}
function setProp (element, key, vlaue) {
element.setAttribute(
// className使用class代替
key === 'className' ? 'class' : key,
vlaue
)
}
将虚拟 DOM 渲染成真实 DOM 后,只需要插入到对应的根节点即可。
const vdom = <div>hello world!!!</div> // h('div', {}, 'hello world!!!')
const app = document.getElementById('app')
const ele = render(vdom)
app.appendChild(ele)
当然在现代化的框架中,一般会有一个组件文件专门用来构造虚拟 DOM,我们模仿 React 使用 class 的方式编写组件,然后渲染到页面中。
class Component {
vdom = null // 组件的虚拟DOM表示
$el = null // 虚拟DOM生成的真实节点
state = {
text: 'Initialize the Component'
}
render() {
const { text } = this.state
return (
<div>{ text }</div>
)
}
}
function createElement (app, component) {
const vdom = component.render()
component.vdom = vdom
component.$el = render(vdom) // 将虚拟 DOM 转换为真实 DOM
app.appendChild(component.$el)
}
const app = document.getElementById('app')
const component = new Component
createElement(app, component)