如何实现一个虚拟DOM

288 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

我正在参加 码上掘金体验活动,详情:show出你的创意代码块

与真实DOM对应的 JavaScript 对象就叫做虚拟 DOM,虚拟可以通过一些巧妙的方法转换为真实 DOM ,操作虚拟 DOM 的代价远小于真实 DOM。今天我们来实现一下虚拟 DOM 的核心逻辑。

首先,我们的虚拟 DOM 是与真实 DOM 有相似性的,所以我们要根据现有的真实 DOM 结构来判断我们创建的虚拟 DOM 的关键属性有哪些。一个真实的 DOM 节点如下:

<div class="class-name">这是一些内容</div>

其中标签名是必须的,类名和文字内容是可选的,类名属于标签名的属性,文字内容是标签的子元素。于是我们根据位置可以大致得出一个虚拟 DOM 对象应该包含至少三个属性:tagName ,props ,children 。

那么,我们可以写个函数创建一个虚拟DOM对象了

const initVDOM = (tagName, { props = {}, children = [] } = {}) => {
  const element = Object.create(null);
  Object.assign(element, {
    tagName,
    props,
    children,
  });

  return element;
}

如上所述,

  • tagName 是一个字符串,他标志着这是某元素,某标签,我们通过 document.createElement(tagName) 来创建对应的元素;
  • props 是元素的属性对象,它的 key-value属性名-属性值 我们通过遍历这个对象,利用 node.setAttribute(key,value) 来为标签添加属性;
  • children 是一个数组,它表示的是元素的子元素集合,子元素可能是一段文字,也可能是一个标签(对象);我们遍历这个数组,对每个子元素做数据类型的判断,若是文字或者数字,那么我们认为它是文本节点,利用 document.createTextNode(child) 来创建文本节点,然后通过 element.appendChild(child) 来加入到父节点的子节点中;如果是元素(对象),那就像他父元素那样走一遍。

按照这个思想,我们可以写一下从虚拟 DOM 转换到真实 DOM 的逻辑函数来了

const render = (vNode) => {
  // 如果虚拟DOM对象是数字或者字符串,那么就是一个文本节点
  if (typeof vNode === 'string' || typeof vNode === 'number') {
    return document.createTextNode(vNode)
  }
  // 元素节点
  // 创建元素节点
  const el = document.createElement(vNode.tagName);
  // 设置属性
  for (let [key, val] of Object.entries(vNode.props)) {
    el.setAttribute(key, val);
  }
  // 设置子元素 子元素是一个数组;它可能是单个文本节点,可能是多个元素节点,但是总而言之它是一个节点数组,所以我们直接遍历这个数组,对子节点先渲染,然后添加到 el 的子节点中
  for (let child of vNode.children) {
    el.appendChild(render(child));
  }
  return el;
}

那么,我们既然现在已经有一个真实的 DOM 节点了,接下来就可以将他挂载到网页中啦!于是我们为这个节点找一个父节点,再写一个简单的挂载函数就可以在页面上看到它了

// html 文件
<body>
  <!-- 父节点 -->
  <div id="vm"></div>
</body>
<script>
  // 这是我们的DOM对象
  const vApp = h('div', {
  props: {
    id: 'app'
  },
  children: [
    'sadfadfa',
    h('h1', {
      props: {
        id: 'title'
      },
      children: ['hello world!']
    })
  ]
})
    
  // 挂载函数
  const mount = ($node, $target) => {
  return $target.appendChild($node)
}
  // 渲染为真实 DOM 节点
  const $app = render(vApp)

  // 挂载我们的DOM节点到id为vm的节点中去
  mount($app, document.getElementById('vm')) // 大功告成

</script>

就这样我们就完成了虚拟 DOM 对象到真实 DOM 节点的渲染、挂载了。但是,有一个很重要的步骤就是更新,现在的网页都是动态更新的,我们这个流程,每次修改 vApp 都会全量的渲染一次整个节点,所以,下期,我们将了解非常重要的diff算法将我们的效率降下来。

下面是本次 demo 的代码片段,可以修改代码中的vApp来查看效果 code.juejin.cn/pen/7088259…

以上就是本期文章的所有内容了,如果你能有什么感觉的话,请给我一些反馈吧,:Accept: 一键三连是最大的鼓[代码片段]励,多谢,我们下期再见!