AICon 上海站|90%日程已就绪,解锁Al未来! 了解详情
写点什么

你不知道的 virtual DOM(一):Virtual Dom 介绍

  • 2020-03-08
  • 本文字数:2393 字

    阅读完需:约 8 分钟

你不知道的virtual DOM(一):Virtual Dom介绍

一、前言

目前最流行的两大前端框架,React 和 Vue,都不约而同的借助 Virtual DOM 技术提高页面的渲染效率。那么,什么是 Virtual DOM?它是通过什么方式去提升页面渲染效率的呢?本系列文章会详细讲解 Virtual DOM 的创建过程,并实现一个简单的 Diff 算法来更新页面。本文的内容脱离于任何的前端框架,只讲最纯粹的 Virtual DOM。敲单词太累了,下文 Virtual DOM 一律用 VD 表示。


这是 VD 系列文章的开篇,后续还会有更多的文章带你深入了解 VD 的奥秘。

二、VD 是什么

本质上来说,VD 只是一个简单的 JS 对象,并且最少包含 tagpropschildren三个属性。不同的框架对这三个属性的命名会有点差别,但表达的意思是一致的。它们分别是标签名(tag)、属性(props)和子元素对象(children)。下面是一个典型的 VD 对象例子:


{  tag: "div",  props: {},  children: [    "Hello World",     {      tag: "ul",      props: {},      children: [{        tag: "li",        props: {          id: 1,          class: "li-1"        },        children: ["第", 1]      }]    }  ]}
复制代码


VD 跟 dom 对象有一一对应的关系,上面的 VD 是由以下的 HTML 生成的:


<div>  Hello World  <ul>    <li id="1" class="li-1">      第1    </li>  </ul></div>
复制代码


一个 dom 对象,比如 li,由 tag(li), props({id:1,class:"li-1"})children(["第",1])三个属性来描述。

三、为什么需要 VD

借助 VD,可以达到有效减少页面渲染次数的目的,从而提高渲染效率。我们先来看下页面的更新一般会经过几个阶段:



从上面的例子中,可以看出页面的呈现会分以下 3 个阶段:


  • JS 计算

  • 生成渲染树

  • 绘制页面


这个例子里面,JS 计算用了 691毫秒,生成渲染树 578毫秒,绘制 73毫秒。如果能有效的减少生成渲染树和绘制所花的时间,更新页面的效率也会随之提高。


通过 VD 的比较,我们可以将多个操作合并成一个批量的操作,从而减少 dom 重排的次数,进而缩短了生成渲染树和绘制所花的时间。至于如何基于 VD 更有效率的更新 dom,是一个很有趣的话题,日后有机会将另写一篇文章介绍。

四、如何实现 VD 与真实 DOM 的映射

我们先从如何生成 VD 说起。借助 JSX 编译器,可以将文件中的 HTML 转化成函数的形式,然后再利用这个函数生成 VD。看下面这个例子:


function render() {  return (    <div>      Hello World      <ul>        <li id="1" class="li-1">          第1        </li>      </ul>    </div>  );}
复制代码


这个函数经过 JSX 编译后,会输出下面的内容:


function render() {  return h(    'div',    null,    'Hello World',    h(      'ul',      null,      h(        'li',        { id: '1', 'class': 'li-1' },        '\u7B2C1'      )    )  );}
复制代码


这里的 h 是一个函数,可以起任意的名字。这个名字通过 babel 进行配置:


// .babelrc文件{ "plugins": [  ["transform-react-jsx", {   "pragma": "h"  // 这里可配置任意的名称  }] ]}
复制代码


接下来,我们只需要定义 h 函数,就能构造出 VD:


function flatten(arr) {  return [].concat.apply([], arr);}
function h(tag, props, ...children) { return { tag, props: props || {}, children: flatten(children) || [] };}
复制代码


h 函数会传入三个或以上的参数,前两个参数一个是标签名,一个是属性对象,从第三个参数开始的其它参数都是 children。children 元素有可能是数组的形式,需要将数组解构一层。比如:


function render() {  return (    <ul>      <li>0</li>      {        [1, 2, 3].map( i => (          <li>{i}</li>        ))      }    </ul>  );}
// JSX编译后function render() { return h( 'ul', null, h( 'li', null, '0' ), /* * 需要将下面这个数组解构出来再放到children数组中 */ [1, 2, 3].map(i => h( 'li', null, i )) );}
复制代码


继续之前的例子。执行 h 函数后,最终会得到如下的 VD 对象:


{  tag: "div",  props: {},  children: [    "Hello World",     {      tag: "ul",      props: {},      children: [{        tag: "li",        props: {          id: 1,          class: "li-1"        },        children: ["第", 1]      }]    }  ]}
复制代码


下一步,通过遍历 VD 对象,生成真实的 dom


// 创建dom元素function createElement(vdom) {  // 如果vdom是字符串或者数字类型,则创建文本节点,比如“Hello World”  if (typeof vdom === 'string' || typeof vdom === 'number') {    return doc.createTextNode(vdom);  }
const {tag, props, children} = vdom;
// 1. 创建元素 const element = doc.createElement(tag);
// 2. 属性赋值 setProps(element, props);
// 3. 创建子元素 // appendChild在执行的时候,会检查当前的this是不是dom对象,因此要bind一下 children.map(createElement) .forEach(element.appendChild.bind(element));
return element;}
// 属性赋值function setProps(element, props) { for (let key in props) { element.setAttribute(key, props[key]); }}
复制代码


createElement函数执行完后,dom 元素就创建完并展示到页面上了(页面比较丑,不要介意…)。


五、总结

本文介绍了 VD 的基本概念,并讲解了如何利用 JSX 编译 HTML 标签,然后生成 VD,进而创建真实 dom 的过程。下一篇文章将会实现一个简单的 VD Diff 算法,找出 2 个 VD 的差异并将更新的元素映射到 dom 中去。


PS: 想看完整代码见这里:


代码(https://gist.github.com/dickenslian/86c4e266ae5f2134373376133bec9e3d)

参考链接:


2020-03-08 19:241844

评论 2 条评论

发布
用户头像
这里不应该是 setProps 吧,应该是 setAttributes
2020-07-29 12:45
回复
哎,没看全,不好意思,忽视。这个方法命名好有歧义 setProps 的内部实现是 setAttributes 😂
2020-07-29 13:06
回复
没有更多了
  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2022-11-25

  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2022-10-17

  • 15|不可变数据:为什么对 React 这么重要?

    我们这节课的主要内容是不可变数据。

    2022-10-04

  • Excel 做数据分析?是真的很强!

    原文链接:怎样用 Excel 做数据分析?

    2022-10-28

  • React 源码分析 2- 深入理解 fiber

    react16 版本之后引入了 fiber,整个架构层面的 调度、协调、diff 算法以及渲染等都与 fiber 密切相关。所以为了更好地讲解后面的内容,需要对 fiber 有个比较清晰的认知。本章将介绍以下内容:

    2023-02-20

  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2023-03-01

  • 30|JavaScript 引擎:双向通讯底层原理是什么?

    双向通讯底层原理是什么?

    2022-11-01

  • React 源码分析 2- 深入理解 fiber

    react16 版本之后引入了 fiber,整个架构层面的 调度、协调、diff 算法以及渲染等都与 fiber 密切相关。所以为了更好地讲解后面的内容,需要对 fiber 有个比较清晰的认知。本章将介绍以下内容:

    2022-12-08

  • 16|单页面应用:如何理解和实现单页面应用开发?

    希望你充分明白单页面路由的技术原理,理解API背后是如何运行的,而不只是停留在vue-router的API使用。

    2022-12-30

  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2022-11-14

  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2023-01-05

  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2023-02-14

  • 深入 React 源码揭开渲染更新流程的面纱

    转前端一年半了,平时接触最多的框架就是React。在熟悉了其用法之后,避免不了想深入了解其实现原理,网上相关源码分析的文章挺多的,但是总感觉不如自己阅读理解来得深刻。于是话了几个周末去了解了一下常用的流程。也是通过这篇文章将自己的个人理解分享出

    2022-11-10

  • 32|Fabric:新渲染器的演进之路

    对核心渲染流程的持续迭代和优化,是 React Native 能够广受欢迎的重要原因之一。

    2023-01-01

  • 加餐|基础篇思考题答疑

    基础篇答疑来了

    2022-12-07

  • Vue 模板是怎样编译的

    这一章我们开始讲模板解析编译:总结来说就是通过compile函数把tamplate解析成render Function形式的字符串compiler/index.js

    2022-11-10

  • 架构实战营 - 模块四作业

    千万级学生管理系统的考试试卷存储方案

    2023-03-05

  • 39|语法扩展:通过 JSX 来做语法扩展

    这节课我们就来看看JSX是如何用在Web UI开发中的。即使你不使用React,这样的模版模式也有很大的借鉴意义。

    2022-12-17

  • Vue 中的 diff 算法深度解析

    模板tamplate经过parse,optimize,generate等一些列操作之后,把AST转为render function code进而生成虚拟VNode,模板编译阶段基本已经完成了,那么这一章,我们来探讨一下Vue中的一个算法策略--dom diff 首先来介绍下什么叫dom diff

    2022-11-08

  • 从源码角度看 React-Hydrate 原理

    React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互

    2022-11-02

发现更多内容

泰山版震撼来袭!阿里巴巴2021年Java程序员面试指导小册已开源

Java架构追梦

Java 架构 面试 金三银四 跳槽

窝家恶补三月,字节跳动三面,终于喜提offer!分享面试感受

Java架构之路

Java 程序员 架构 面试 编程语言

什么是供应链,供应链有哪些核心指标

学志

技术 指标体系 供应链 电商平台

使用doom-emacs三个月后, 春节期间从零配置一份自己的emacs(附详细文档)

lmymirror

阿里巴巴云原生应用安全防护实践与 OpenKruise 的新领域

阿里巴巴云原生

容器 运维 云原生 k8s 调度

2021最新百度/平安/蚂蚁金服/腾讯/拼多多面经总结(附答案解析)

比伯

Java 编程 架构 面试 计算机

为图片添加Emoji,微信这隐藏功能让你不花冤枉钱

彭宏豪95

微信 效率 效率工具 emoji

话题讨论 | mongodb拥有十大核心优势,为何国内知名度不是很高?

杨亚洲(专注MongoDB及高性能中间件)

MySQL 数据库 mongodb 话题讨论 分布式数据库mongodb

Laravel来信|Event

LeastCoding

laravel Event 观察者模式

2021阿里总监最新手码BAT等大厂面经!GitHub已标星86.2K

比伯

Java 编程 架构 面试 程序人生

电力行业区块链技术应用和产业布局

CECBC

区块链

话题讨论 | 程序员是做前端开发好,还是后端开发好呢?

xcbeyond

程序人生 话题讨论

有赞 Flink 实时任务资源优化探索与实践

Apache Flink

flink

流媒体传输协议之 RTMP

阿里云CloudImagine

TCP 音视频 RTMP 传输协议 流媒体;

算力蜂系统开发|算力蜂软件APP开发

系统开发

少儿学编程系列---如何使用turtle画风车

cloudcoder

微信十年,弹指一挥间

彭宏豪95

微信 产品 互联网 写作

话题讨论 | 比特币攻击重现江湖,你准备好了吗?

程序员架构进阶

话题讨论 28天写作 2月春节不断更 话题王者 勒索攻击

GitHub上已获赞百万!阿里架构师10年磨一剑打造的Java面试小抄(2021版)开源分享

Java架构师迁哥

架构设计篇之微服务实战笔记(三)

小诚信驿站

架构师 刘晓成 小诚信驿站 28天写作 架构师成长笔记

Java岗四面字节跳动成功之前,我都刷了那些面试题以及做了那些准备!

Java架构之路

Java 程序员 架构 面试 编程语言

人人矿场APP开发|人人矿场系统软件开发

系统开发

区块链电子证照应用平台,区块链电子证照平台建设方案

13530558032

翻译:《实用的Python编程》02_03_Formatting

codists

Python 人工智能 后端 数据结构与算法 格式化

程序员成长第十一篇:弄懂需求

石云升

需求 28天写作 2月春节不断更

区块链药品溯源平台-区块链医药追踪溯源

13530558032

话题讨论 | 现实中程序员是怎样飞快敲代码的?

xcbeyond

程序人生 话题讨论

什么!?金三银四,2021年阿里最新面试题惨遭泄露?

Java架构之路

Java 程序员 架构 面试 编程语言

一文读懂区块链产业最新发展趋势

CECBC

大数据

电影台词反向搜索视频片段,这个工具也太好用了吧|33 台词

彭宏豪95

效率 效率工具 电影

颠覆技术-智能合约的说明文

CECBC

区块链

你不知道的virtual DOM(一):Virtual Dom介绍_文化 & 方法_大白_InfoQ精选文章