写点什么

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

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

    阅读完需:约 8 分钟

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

AI 大模型超全落地场景&金融应用实践,8 月 16 - 19 日 FCon x AICon 大会联诀来袭、干货翻倍!

一、前言

目前最流行的两大前端框架,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:241678

评论 2 条评论

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

Linux中有趣的命令:cowsay,会说话的牛!

wljslmz

Linux 6月月更 cowsay

Java培训多线程+List分段解决批量更新太慢

@零度

List 多线程 JAVA开发

苹果称M2比intel i5强26倍 虚假营销的实情揭晓!

科技之家

InfoQ 极客传媒 15 周年庆征文|我为InfoQ写作社区定制一款机械键盘庆生

法医

前端 InfoQ极客传媒15周年庆

知名网络安全硬件平台厂商铵泰克加入龙蜥社区

OpenAnolis小助手

开源 网络安全 龙蜥社区 CLA 铵泰克

Plugsched 实战解读:如何在不中断业务时对 Linux 内核调度器热升级? | 龙蜥技术

OpenAnolis小助手

Linux 开源 内核 调度 Plugsched

柴云鹏:创新能力的培养至关重要|OceanBase 数据库大赛访谈

OceanBase 数据库

oceanbase 数据库大赛

LP流动性挖矿系统开发生态系统详解

开发微hkkf5566

评“开发人员不喜欢低代码和无代码的8个理由”

代码制造者

程序员 编程语言 开发 iVX 低代码开发

喜报 | 旺链科技签约汨罗市文旅体产业项目,打造“链”上数字乡村

旺链科技

区块链 产业区块链 乡村振兴 汨罗市

使用 ViroReact 开发增强实现应用的一个具体例子

汪子熙

AR React 增强现实 6月月更

Flink CDC + Hudi 海量数据入湖在顺丰的实践

Apache Flink

大数据 flink 编程 流计算 实时计算

物联网低代码平台如何使用操作日志?

AIRIOT

物联网 低代码开发 低代码平台 物联网关

kube-apiserver调度器核心实现

申屠鹏会

k8s

精益产品开发体系最佳实践及原则

阿里云云效

云计算 阿里云 精益开发 产品开发 开发

Java 对象如何安全的 toString

HoneyMoose

TiDB Cloud 上线 Google Cloud Marketplace,以全新一栈式实时 HTAP 数据库赋能全球开发者

PingCAP

TiDB

【直播回顾】Hello HarmonyOS应用篇第六课——短视频应用开发

HarmonyOS开发者

HarmonyOS

保姆级教程:如何成为Apache Linkis文档贡献者

康月牙

Apache GitHub 教程 文档 Linkis

Android 产生ANR后的Trace文件的解析

北洋

android 6月月更

GameFi新的启程,AQUANEE将于6.9日登陆Gate以及BitMart

西柚子

最佳实践 | 用腾讯云AI语音识别零基础实现小程序语音输入法

牵着蜗牛去散步

最佳实践 语音识别 小程序开发 腾讯云AI 语音输入法

文档书写规范

甜甜的白桃

文档 6月月更

【高并发】彻底理解Nginx限流机制与实战

冰河

并发编程 多线程 高并发 异步编程 6月月更

盘点现有开源软件许可合规工具

开源社

使用 KubeKey 搭建 Kubernetes/KubeSphere 环境的“心路(累)历程“

胡说云原生

Kubernetes KubeSphere KubeKey

IPO,联结一切的桥梁

鼎道智联

《数字经济全景白皮书》银行财富管理篇 重磅发布

易观分析

理财 银行理财

Nacos配置中心实战,盘古微服务开发标配组件

码农大熊

微服务架构 nacos 盘古开发框架 分布式开发

版式设计三大原则

源字节1号

软件开发 小程序开发

web前端培训React如何原生实现防抖

@零度

前端开发 React

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