什么是虚拟DOM?

古老的渲染方式(innerHTML)

在虚拟DOM出现之前,我们创建页面UI最常用的方式就是innerHTML,但是它有一个很大的问题,就是会导致很多不必要的性能开销。

看下面这段代码,这是个很经典的渲染服务器返回的列表数据到HTML中:

const dataList = [
  { label: 'Lorem, ipsum.', value: 112.7 },
  { label: 'Praesentium, facere.', value: 96.22 },
  { label: 'Rerum, repudiandae.', value: 144.13 },
]
for (let i = 0; i < dataList.length; i++) {
  containerDiv.innerHTML += `
    
${ dataList[i].label }
${ dataList[i].value }
` }

它会如何执行呢?首先将模板字符串解析成 DOM 树,这是一个 DOM 层面的计算。DOM运算是非常消耗性能的,相比于纯JavaScript层面的计算来说是非常低效的。

然后,在for循环中每一次的循环都重新设置了innerHTML,重新设置 innerHTML 属性就等价于销毁所有旧的 DOM 元素,然后再全量创建新的 DOM 元素。

上面的代码中只是一个数量少的,结构复杂度低的示例,假如你要处理的数据量多达几千条,结构复杂度稍微高一点,那么你就能很明显的感觉出性能的开销。

虚拟DOM的作用(VNode)

既然问题有了,那么就一定会出现对应的解决方案,而虚拟DOM就是其中之一。根据上一个代码示例可以看出,直接使用innerHTML的方式来渲染DOM结构最大的问题在于:重复(没有变化)的结构被重新渲染了N次。

那有没有一种方法可以让我们只重新渲染变化的部分呢?而没变化的部分就不要重新渲染了。当然有,虚拟DOM就是为了解决这个问题而出现的。

虚拟 DOM 创建页面UI的过程分为两步:

  • 第一步是创建 JavaScript 对象,这个对象可以理解为真实 DOM 的描述(VNode)
  • 第二步是递归地遍历虚拟 DOM 树并创建真实 DOM。

虚拟DOM重新创建 JavaScript 对象(虚拟 DOM 树),然后比较新旧虚拟 DOM,找到变化的元素并更新它,也就是说虚拟DOM只会重新渲染差异部分。

渲染器(renderer)

Vue组件都是依赖渲染器来工作的,渲染器的工作原理就是使用一些我们熟悉的 DOM 操作 API 来完成渲染工作。

假设有如下虚拟DOM结构:

const vnode = {
  tag: 'button',
  children: [
    {
      tag: 'b',
      children: 'Submit'
    }
  ]
}

然后编写一个渲染器,把上面这段虚拟 DOM 渲染为真实 DOM。

function renderer(vnode, container) {
  // 提取顶层元素
  const el = document.createElement(vnode.tag)
  // 遍历props
  for (const key in vnode.props) {
    // 提取事件
    if (/^on/.test(key)) {
      el.addEventListener(
        key.substr(2).toLowerCase(),
        vnode.props[key]
      )
    }
    // 提取属性
    el[key] = vnode.props[key]
  }

  // 处理children
  if (typeof vnode.children === 'string') {
    el.appendChild(
      document.createTextNode(vnode.children)
    )
  } else {
    vnode.children.forEach(child => renderer(child, el))
  }

  // 挂载元素
  container.appendChild(el)
}

上面只是一段我们自己DIY的渲染器,现在我们用vue的渲染器来实现真实DOM的渲染:

  • h(tag, props, children)
import { h, createApp } from './vue.esm-browser.prod.js'

const app = createApp({
  render() {
    return h('button', null, [
      h('b', null, 'Submit')
    ])
  }
})
app.mount('#app')

你可能感兴趣的:(什么是虚拟DOM?)