Virtual DOM

1,278 阅读3分钟

什么是虚拟DOM

简单地说虚拟DOM其实就是使用JavaScript对象的形式来一个DOM节点,包含了typepropschildren三个属性:

<div class="container">
  hello world
</div>

将👆上面的HTML代码以虚拟DOM的形式表示:

{
  type: 'div',
  props: {
    className: 'container'
  },
  children: [
    "hello world"
  ]
}

因为DOM树🌲是一个树形的结构,所以使用JavaScript对象就可以表示出树的结果。HTML和虚拟DOM有点类似于XMLJSON,使用不同的形式来表示相同的数据。

虚拟DOM的好处

需要注意的是虚拟DOM不一定比真实的DOM操作快JavaScript引擎和DOM引擎使用的是同一个主线程,任何涉及到DOM的操作都需要先把JavaScript的数据结构转换为DOM的数据结构,再将JavaScript引擎挂起执行DOM引擎,执行完成后再切换执行JavaScript引擎,这种上下文的切换是很消耗性能的,所以解决DOM操作的性能问题的关键在于减少不必要的DOM操作

那么虚拟DOM没有带来任何的性能的优化吗?也不是这样!虚拟DOM能够实现最细粒度的更新你的DOM,对于DOM操作我更新DOM的常见做法是使用innerHTML,但是innerHTML的JavaScript计算和DOM操作通常和你的界面数据大小挂钩,即innerHTML的时间复杂度O = JavaScript操作时间 + 重新创建所有DOM元素的时间;而虚拟DOM更新UI界面的时间复杂度O = 渲染虚拟DOM + diff + 必要的DOM更新,渲染虚拟DOM和diff操作都是JavaScript计算不会涉及到JavaScript引擎和DOM引擎的上下文切换。所以虚拟DOM不管每次的数据变化是怎样的,每次重绘的对于DOM的操作都是最小的。

虚拟DOM最大的好处在于抽象了渲染的过程,为应用带来了跨平台的能力,不再是仅仅局限于浏览器端。比如React-NativeWeeX可以运行在Android、IOS平台上。

真实DOM到虚拟DOM的映射

借助@babel/plugin-transform-react-jsx可以实现从真实DOM到虚拟DOM的转换。

1.安装babel依赖:npm i -D @babel/cli @babel/core @babel/plugin-transform-react-jsx

2.配置 .babelrc:

// .babelrc文件
{
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "h"
    }]
  ]
}

下面👇就可以借助babel来将真实DOM转换为虚拟DOM

新建index.js文件📃:

function toVDOM() {
  return (
    <ul className="group">
      <li className="item">吃饭</li>
      <li className="item">睡觉</li>
      <li className="item">打代码</li>
    </ul>
  )
}

执行命令npx babel main.jsx --out-file vdom.js后得到:

function toVDOM() {
  return h("ul", {
    className: "group"
  }, h("li", {
    className: "item"
  }, "吃饭"), h("li", {
    className: "item"
  }, "睡觉"), h("li", {
    className: "item"
  }, "打代码"));
}

所以我们只需要实现h函数就能得到虚拟DOM了。

function h(type, props, ...children) {
  return {
    type,
    props: props || {},
    children: children.flat()
  }
}

render函数

上面我们已经实现了真实DOM到虚拟DOM的转换,接下来我们将实现render函数将虚拟DOM渲染成真实DOM

function render(element, container) {
  const { type, props = {}, children = [] } = element;
  const dom = typeof element === 'number' || typeof element === 'string' ? document.createTextNode(element) : document.createElement(type);

  Object.keys(props).forEach(p => {
    dom[p] = props[p]
  });

  children.forEach(c => {
    render(c, dom)
  });

  container.appendChild(dom);
}

将通过h函数生成的虚拟DOM传入到render函数中就能实现将虚拟DOM渲染成真实DOM了:

render(element, document.getElementById('app'))

接下来的更新写作内容

  • 虚拟DOM的更新