虚拟DOM如何进化为真实DOM

开发 前端
Vue和React的Render函数中都涉及到了Virtual DOM的概念,Virtual DOM也是性能优化上的重要一环,同时突破了直接操作真实DOM的瓶颈,本文带着以下几个问题来阐述Virtual DOM。

前言

Vue和React的Render函数中都涉及到了Virtual DOM的概念,Virtual DOM也是性能优化上的重要一环,同时突破了直接操作真实DOM的瓶颈,本文带着以下几个问题来阐述Virtual DOM。

1.为什么要操作虚拟 DOM?

2.什么是虚拟 DOM?

3.手把手教你实现虚拟 DOM 渲染真实 DOM

希望阅读本文之后,能够让你深入的了解虚拟 DOM并且在开发和面试中收益。

为什么要操作虚拟 DOM

为了帮助我们更好的理解为什么要操作虚拟 DOM,我们先从浏览器渲染[1]一个 HTML 文件需要做哪些事情说起:


浏览器渲染机制大致可以分为以下 5 步走:

1.创建 DOM tree

2.创建 Style Rules

3.构建 Render tree

4.布局 Layout

5.绘制 Painting

我们过去使用原生JavaScript和jquery去操作真实DOM的时候,浏览器会从构建 DOM🌲 开始从头到尾的执行一遍渲染的流程。

在一次开发中,假如产品告诉你一个需求,你需要在一次操作中更新10个DOM节点,理想状态是浏览器一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新 DOM 请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行 10 次流程。

过了一会产品经理把你叫过去和你说把需求改一下,此时你又需要操作一次 DOM 的更新,那么这个时候之前做的 10 次 DOM 操作就是白白浪费性能,浪费感情。

即使计算机硬件一直在更新迭代,但是操作DOM的代价仍旧是昂贵的,频繁操作 DOM 还是会出现页面卡顿,影响用户的体验。真实的 DOM 节点,哪怕一个最简单的 div 也包含着很多属性,可以打印出来直观感受一下:


如此多的属性,如果每次对 DOM 结构都进行更新,一次,两次,三次...一百次....一千次...,可想而知,是多么庞大的数据量。

因此虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有 10 次更新 DOM 的动作,虚拟DOM不会立即操作DOM,而是将这 10 次更新 DOM 的动作通过Diff算法最终生成一个js对象,然后通知浏览器去执行一次绘制工作,这样可以避免大量的无谓的计算量。

什么是虚拟 DOM

虚拟 DOM[2]就是我们上面所说的js对象。

其本质上就是在JS和DOM之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM),直接操作内存中的 JS 对象的速度显然要更快。

  1. function vnode(tag, data, key, children, text) { 
  2.     return { 
  3.         tag, 
  4.         data, 
  5.         key
  6.         children, 
  7.         text 
  8.     } 

举个栗子:

假如我们有这样的一个 DOM 树

  1. <ul class="list"
  2.   <li class="item">前端简报</li> 
  3.   <li>vue</li> 
  4. </ul> 

 那么,我们怎么用 js 的对象来对应到这个树呢?

  1.     tag: 'ul',        // 元素标签 
  2.     data: {           // 属性 
  3.         class: 'list' 
  4.     }, 
  5.     key''
  6.     text: '',  // 文本内容 
  7.     children: [ 
  8.         { 
  9.             tag: "li"
  10.             data: { 
  11.                 class: "item" 
  12.             }, 
  13.             key''
  14.             text: ''
  15.             children: [ 
  16.                 { 
  17.                     tag: undefined, 
  18.                     data: undefined, 
  19.                     key: undefined, 
  20.                     text: '前端简报'
  21.                     children: [] 
  22.                 } 
  23.             ] 
  24.         }, 
  25.         { 
  26.             tag: "li"
  27.             data: ""
  28.             key''
  29.             text: ''
  30.             children: [ 
  31.                 { 
  32.                     tag: undefined, 
  33.                     data: undefined, 
  34.                     key: undefined, 
  35.                     text: 'vue'
  36.                     children: [] 
  37.                 } 
  38.             ] 
  39.         } 
  40.     ]       // 子元素 

由此可知:DOM tree的信息都可以用JavaScript对象来表示,反过来,我们也可以用 JavaScript对象表示的树结构来构建一棵真正的DOM树。

实现虚拟 DOM 渲染真实 DOM

有了JavaScript对象之后如何转化为真实的 DOM 树结构呢?

ul 和 li 在 js 对象中,页面上并没有此结构,所以我们需要把ul和li转化为和

标签

而文本标签我们定义 Vnode 为:

  1.    tag: undefined, 
  2.    data: undefined, 
  3.    key: undefined, 
  4.    text: 'vue'
  5.    children: [] 

故可以判断tag的类型来确定创建元素的类型.

  1. function createElm(vnode) { 
  2.     let { tag, data, children, key, text } = vnode; 
  3.  
  4.     if (typeof tag == "string") { 
  5.         vnode.el = document.createElement(tag);  //创建元素放到vnode.el上 
  6.         children.forEach(child => { 
  7.             vnode.el.appendChild(createElm(child)) 
  8.         }) 
  9.     } else { 
  10.         vnode.el = document.createTextNode(text);  //创建文本 
  11.     } 
  12.     return vnode.el 

如果子节点存在并且也是虚拟DOM的话,我们通过递归调用创建子节点。


创建 DOM 树结构之后我们需要设置节点的属性,即处理虚拟 DOM 中的data属性。

  1. function updateProperties(vnode) { 
  2.     let el = vnode.el; 
  3.     let newProps = vnode.data || {}; 
  4.     for (let key in newProps) { 
  5.         if (key == "style") { 
  6.             for (let styleName in newProps.style) { 
  7.                 el.style[styleName] = newProps.style[styleName]; 
  8.             } 
  9.         } else if (key == "class") { 
  10.             el.className = newProps.class; 
  11.         } else { 
  12.             el.setAttribute(key, newProps[key]); 
  13.         } 
  14.     } 

在我们创建元素标签之后调用updateProperties方法即可


把上面创建出来的真实 DOM 结构 vnode.el 添加到文档当中即可呈现出我们需要的真实 DOM 结构

  1. let parentElm = document.getElementById("app").parentNode; 获取之前app的父亲body 
  2. parentElm.insertBefore(createElm(vnode), document.getElementById("app").nextSibling); //body里在老的app后面插入真实dom 
  3. parentElm.removeChild(document.getElementById("app")); //删除老的节点 

 

总结

以上就是本文的全部内容,我想我们现在应该了解什么是虚拟DOM的概念了以及虚拟DOM是如何实现真实DOM渲染的。其中用到了主要用到了子节点的递归,下篇文章将讲解虚拟节点的 diff 算法,敬请期待。

参考资料
[1]虚拟DOM介绍: https://www.jianshu.com/p/616999666920

[2]如何实现一个 Virtual DOM 算法: 'https://github.com/livoras/blog/issues/13'

【编辑推荐】

 

责任编辑:姜华 来源: 前端简报
相关推荐

2021-08-16 09:59:52

ReactSvelte开发

2021-01-11 07:51:16

DOM对象节点树

2010-09-28 11:11:23

XML DOMHTML DOM

2021-01-28 07:21:13

算法虚拟DOM前端

2010-09-28 14:44:56

遍历DOM

2023-02-14 09:37:00

Vue无虚拟模式

2021-12-12 18:31:35

VNode组件Vue3

2023-12-26 10:12:19

虚拟DOM数据

2010-09-09 17:19:07

HTML DOMXML DOM

2021-04-09 18:01:03

前端ReactDOM

2022-04-19 23:01:54

Vue.jsDOM节点DOM树

2015-07-10 09:24:16

用友

2010-09-08 16:50:11

JavaScriptDOM操作

2010-09-28 10:24:50

HTML DOMXML DOM

2024-02-28 13:41:00

GenAI数据治理LLM

2021-08-31 07:02:20

Diff算法DOM

2010-09-08 17:26:46

JavaScript

2010-09-28 09:33:25

DOM模型

2014-05-26 16:16:59

Shadow DomWeb Compone

2022-05-06 07:19:11

DOMDiff算法
点赞
收藏

51CTO技术栈公众号