浅谈虚拟DOM
Posted 前端技术潮流
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈虚拟DOM相关的知识,希望对你有一定的参考价值。
目前前端开发框架 2 巨头React
和Vue
都使用到了虚拟 DOM(virtual dom)技术,以及现在面试基本都会问到的问题:你了解虚拟 DOM 吗?那么,现在我就来简单的聊一聊虚拟 DOM
为什么要用虚拟 DOM
在 web 开发中,操作 DOM 是非常损耗性能的,由于浏览器 JS 是单进程,如果在页面渲染方面占用了太长时间,那么在页面响应方面就会堵塞,十分影响用户体验。同时,现代 web 应用越来越庞大,功能也更加复杂,以及 AJAX 的使用,页面也能响应更多的用户操作,页面的改变也越来越大,如果使用以前的 JQuery,那么 web 开发工作将会越发复杂,需要不停的响应用户的操作,更新数据,更新页面,从开发维护到应用性能上都很受影响。
如何提高开发效率,减少维护成本以及提高 web 应用性能呢?React
给我们带来了virtual dom
什么是虚拟 DOM
虚拟 DOM 就是使用 JS 模拟出来的 DOM 结果,比如一段简单的 html:
<div id="title">this is title</div>
使用 JS 我们可以这样表示:
{
"tagName": "div",
"props": {
"id": "title"
},
"children": ["this is title"]
}
对于更复杂的 HTML 标签,我们在 JS 里都可以用以上的结构来模拟:tagName
、props
和children
。这样,我们就可以用 JS 来描述页面 UI 了。
如何解析虚拟 DOM
有了虚拟 DOM 之后,我们页面要呈现出来还是需要转换为 HTML 标签才行,那么现在就是如何解析虚拟 DOM 了,现在,我们有这样一段 VNode:
const vNode = {
tagName: 'div',
props: null,
children: [
{
tagName: 'p',
props: {
class: 'list p',
onClick: e => {
console.log(e)
}
},
children: ['apple']
},
{
tagName: 'p',
props: { class: 'list p' },
children: ['orange']
}
]
}
通过分析可以发现,这是一个 DIV 标签,同时含有 2 个 P 标签,我们首先通过tagName
创建DOMElement
,如果没有tagName
我们创建TextNode
,然后为 Element 添加属性以及子元素,我们可以设计一个 render 函数来解析它,render 函数接收 vNode 以及解析后挂载的位置:
function render(vNode, dom) {
// 文本标签
if (isString(vNode)) {
const text = document.createTextNode(vNode)
dom.appendChild(text)
// 数组
} else if (isArray(vNode)) {
vNode.forEach(child => render(child, dom))
// 对象
} else if (isObject(vNode)) {
const { tagName, props, children } = vNode
const root = document.createElement(tagName)
dom.appendChild(root)
if (props) {
addAttribute(root, props)
}
if (isArray(children)) {
children.forEach(child => render(child, root))
}
}
}
这里还有一些判断 JS 类型的工具函数:
const isArray = arr => Object.prototype.toString.call(arr) === '[object Array]'
const isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
const isString = str => Object.prototype.toString.call(str) === '[object String]'
在属性解析里面,我们还可能会有事件需要设置,我们编写一个简单的属性设置函数:
function addAttribute(el, props) {
Object.keys(props).forEach(key => {
const val = props[key]
// 添加事件
if (key.startsWith('on')) {
const eventName = key.toLowerCase().slice(2)
if (eventName) {
el.addEventListener(eventName, val, false)
}
} else {
el.setAttribute(key, val)
}
})
}
使用虚拟 DOM
这样一个简单的虚拟 DOM 解析函数就写好了,它可以根据虚拟 DOM 渲染成真实的 UI 界面,如何测试并使用它呢?我们可以使用 Jason Miller 开发的 htm[1],它可以无需编译即可在浏览器中使用,十分简单,使用如下:
import htm from '../src/index.mjs'
function h(tagName, props, ...children) {
return { tagName, props, children }
}
const html = htm.bind(h)
let fruits = ['apple', 'orange']
const handleClick = (e, index) => {
console.log(e, index)
}
const vNode = html`
<div>
${fruits.map(
(f, i) =>
html`
<p class="list p" onClick=${e => handleClick(e, i)}>${f}</p>
`
)}
</div>
`
render(vNode, document.body)
这样就可以轻松把我们的虚拟 DOM 渲染到页面上了。但是仅仅这样使用的话还不如直接插入innerHTML
,在效率上并没有很大的提高,其实虚拟 DOM 的优势还需要结合diff
算法才能体现,现在我们只是实现了虚拟 DOM 的解析工作。
其实,虚拟 DOM 真正的意义在于使用函数式来开发 UI,使得UI=h(vNode)
,其中的 h 函数就是我们解析 VNOde 的函数,并且其他与 UI 相关的绘制工作我们都可以使用函数式来完成,唯一需要修改的就是我们的解析函数了,无论怎么样UI=h(vNode)
References
[1]
htm: https://github.com/developit/htm
喜欢就关注我吧↓
以上是关于浅谈虚拟DOM的主要内容,如果未能解决你的问题,请参考以下文章