浅谈虚拟DOM

Posted 前端技术潮流

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈虚拟DOM相关的知识,希望对你有一定的参考价值。

目前前端开发框架 2 巨头ReactVue都使用到了虚拟 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 里都可以用以上的结构来模拟:tagNamepropschildren。这样,我们就可以用 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的主要内容,如果未能解决你的问题,请参考以下文章

浅谈虚拟DOM

前端知识 |浅谈React setState

一起来玩一个模拟游戏吧:浅谈虚拟DOM

2.ReactJS基础(虚拟DOM,JSX语法)

浅谈DOM数遍历

浅谈React工作原理