什么是虚拟dom
真实dom大家应该都知道,就是html的文档对象呗,文档中的每个元素都对应一个真实dom对象,通过操作真实dom对象,我们可以获取、修改、添加和删除HTML元素。
那么问题来了,什么是虚拟dom?在我看来,虚拟dom就是存储页面元素信息的一个对象,它和真实dom一样都存储了元素的内容、元素的属性、元素之间的结构等信息。但不同的是,虚拟dom的属性更加精简,而且虚拟dom不能直接渲染,需要转化成真实dom才能渲染。
如下图所示,我在控制台打印了一个div元素的真实dom,可以看到这个对象有着非常多的属性。操作一个如此庞大的对象,代价是昂贵的。尤其是数据频繁变动的时候,每次数据变动都要去修改dom,很有可能会引起性能问题。但如果引进虚拟dom的话,数据发生变动时,我们先去修改虚拟dom,由于虚拟dom对象的体量很小,操作速度很快,短时间内多次修改也不会有太大的性能问题,最终将多次修改的结果一次性地同步到真实dom上,这样就只需要操作一次真实dom,性能大大优化。
虚拟dom的实现
下面我将实现一个简单虚拟dom对象的创建和渲染。
下面代码中的Vdom类,就是我所定义的虚拟dom类,类里面有三个属性tagName、attrs、children,它们的含义分别是标签名、标签属性、子元素。
Vdom有一个成员方法render(),此方法的职责是渲染这个Vdom对象,它的返回值一个真实的dom对象。它的大致流程是这样的:
- 如果tagName是’text’,那说明这是一个文本节点,则调用document.createTextNode()创建一个文本节点对象并返回
- 如果tagName不是’text’,那说明这是一个html元素,则调用document.createElement()创建一个dom对象,并遍历attrs设置该dom对象的属性值。然后遍历children数组,为子元素创建Vdom对象并渲染,把子元素的真实dom对象添加进来。最后return即可。
class Vdom{
constructor(tagName, attrs, children){
this.tagName = tagName
this.attrs = attrs || {}
this.children = children || []
}
render(){
let htmlElement
if(this.tagName == 'text'){
htmlElement = document.createTextNode(this.attrs['nodeValue'])
}
else {
htmlElement = document.createElement(this.tagName)
// 设置真实dom的属性
for(let key in this.attrs){
// 很怪,直接htmlElement.style = this.attrs['style']不起作用
if(key === 'style'){
for(let styleKey in this.attrs['style']){
htmlElement.style[styleKey] = this.attrs['style'][styleKey]
}
}
else {
htmlElement[key] = this.attrs[key]
}
}
// 为子元素创建真实dom,并添加到当前dom下
for(let child of this.children){
let childVdom = new Vdom(child.tagName, child.attrs, child.children)
htmlElement.append(childVdom.render())
}
}
return htmlElement
}
}
最后,写代码来测试一下。
下面是我的测试代码,可以看到我调用Vdom类创建了一系列虚拟dom对象,它们之间有着多层级的嵌套和复用关系,最后我把虚拟dom转换成真实dom并添加到body元素下,在浏览器中运行的效果如图所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./vdom.js"></script>
<script>
let textVdom = new Vdom('text', {nodeValue: '你好,再见'})
let h1Vdom = new Vdom('h1', {style: {color: 'red'}}, [textVdom])
let liVdom = new Vdom('li', {style: {color: 'white'}}, [textVdom])
let pVdom = new Vdom(
'p',
{style: {width: '200px', height: '200px', 'background-color': 'purple'}},
[h1Vdom, liVdom]
)
let divVdom = new Vdom(
'div',
{style: {width: '320px', height: '320px', 'background-color': 'grey'}},
[h1Vdom, liVdom, pVdom]
)
console.dir(divVdom)
// 渲染,把虚拟dom转换成真实dom
let htmlDivElement = divVdom.render()
document.querySelector('body').append(htmlDivElement)
</script>
</body>
</html>