Zhili's blog Zhili's blog
首页
关于
GitHub (opens new window)

Zhili

转型中的前端老兵
首页
关于
GitHub (opens new window)
  • webpack 区分环境使用CDN以及HtmlWebpackPlugin插件的编写
  • 使用 Mpvue 开发小程序总结
  • 前端知识汇总-持续更新
  • webGL知识汇总-持续更新
  • React思想要点
  • Mapbox 入门初试
  • 构建自己的 GLSL 绘图器 - 2d 版
  • webGL二维有向距离场(SDF)及布尔运算
  • webGL入门:绘制一个三角形
  • 使用 GLSL 绘图尝试:绘制正弦曲线
  • 简单解析虚拟 DOM
    • 自己动手开发一个 markdown 转微信文章工具
    • Vue组件扩展及权限管理的实现技巧
    • 使用 CSS 绘制三角形
    • Nginx 前端配置和使用
    • webpack按需加载配置和babel编译
    • 面试总结39题
    • 使用node反向代理接口改造旧项目
    • 可视化表单搭建系统的开发与思考
    • 前端
    zhili
    2019-08-26

    简单解析虚拟 DOM

    虚拟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>
    
    1

    使用 JS 我们可以这样表示:

    {
      "tagName": "div",
      "props": {
        "id": "title"
      },
      "children": ["this is title"]
    }
    
    1
    2
    3
    4
    5
    6
    7

    对于更复杂的 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']
        }
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    通过分析可以发现,这是一个 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))
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    这里还有一些判断 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]'
    
    1
    2
    3

    在属性解析里面,我们还可能会有事件需要设置,我们编写一个简单的属性设置函数:

    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)
        }
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 使用虚拟 DOM

    这样一个简单的虚拟 DOM 解析函数就写好了,它可以根据虚拟 DOM 渲染成真实的 UI 界面,如何测试并使用它呢?我们可以使用 Jason Miller 开发的 htm (opens new window),它可以无需编译即可在浏览器中使用,十分简单,使用如下:

    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)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    这样就可以轻松把我们的虚拟 DOM 渲染到页面上了。但是仅仅这样使用的话还不如直接插入innerHTML,在效率上并没有很大的提高,其实虚拟 DOM 的优势还需要结合diff算法才能体现,现在我们只是实现了虚拟 DOM 的解析工作。

    以上只是一个简单的虚拟 DOM 示例,实际中,我们可能还会包括类组件,函数组件等,我们需要对他们进行一一的解析,后面将会逐步展开。

    其实,虚拟 DOM 真正的意义在于使用函数式来开发 UI,使得UI=h(vNode),其中的 h 函数就是我们解析 VNOde 的函数,并且其他与 UI 相关的绘制工作我们都可以使用函数式来完成,唯一需要修改的就是我们的解析函数了,无论怎么样UI=h(vNode)

    编辑 (opens new window)
    #虚拟DOM#JavaScript
    上次更新: 2022/03/14, 06:54:51
    使用 GLSL 绘图尝试:绘制正弦曲线
    自己动手开发一个 markdown 转微信文章工具

    ← 使用 GLSL 绘图尝试:绘制正弦曲线 自己动手开发一个 markdown 转微信文章工具→

    最近更新
    01
    可视化表单搭建系统的开发与思考
    03-09
    02
    使用node反向代理接口改造旧项目
    11-17
    03
    面试总结39题
    10-11
    更多文章>
    Theme by Vdoing | Copyright © 2022-2023 zhili | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式