QCon 全球软件开发大会倒计时2个月!讲师已确认60%+ >>> 了解详情
写点什么

你不知道的 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:241619

评论 2 条评论

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

移动应用崛起新契机—超级app+轻应用!

Geek_2305a8

大白话—70个你必须知道的AI重要概念(二)

Geek_2305a8

【第七在线】服装企业对于智能商品计划系统的误区有哪些?

第七在线

使用 Taro 开发鸿蒙原生应用 —— 快速上手,鸿蒙应用开发指南

京东科技开发者

基于企业级SaaS低代码平台的协同制造产品解决方案

万界星空科技

低代码 MES系统 低代码开发 万界星空科技 低代码云MES

ERP对接MES的3种类型的接口

万界星空科技

低代码 MES系统 系统集成 万界星空科技 系统对接

AI数字人给教育行业带来机会!

青否数字人

操作系统寒武纪 :鸿蒙来了企业IT高兴吗?

FN0

鸿蒙 操作系统 HarmonyOS

使用API接口获取拼多多商品详情

Noah

Video-LLaMA:为AI大模型注入视听觉

百度开发者中心

人工智能 大模型

Code Llama:基于 Llama 2 的 AI 代码生成大模型

百度开发者中心

人工智能 大模型 代码生成

AI数字人源码部署,打造自己品牌!

青否数字人

千万级数据深分页查询SQL性能优化实践

京东科技开发者

DxO PureRAW for Mac(RAW照片处理器)v3.9.0中文激活版

影影绰绰一往直前

深入了解LLaMA大模型

百度开发者中心

深度学习 nlp 大模型

重磅!参编我国首个图计算平台标准 引领图数据库行业新机遇

百度安全

管理型人格特质揭秘:适合测试管理的人应具备何种素质

测吧(北京)科技有限公司

测试

IntelliJ IDEA安装教程

小魏写代码

数据展现之道:精心打造可在线浏览的动态数据报表

EquatorCoco

数据库 动态数据

如何提升时序数据多表低频场景的写入性能?一文详解

TDengine

tdengine 时序数据库

类似 Microsoft Project 的12款在线项目管理工具

PingCode

项目管理 项目经理 项目管理工具

鸿蒙HarmonyOS实战-ArkTS语言(基本语法)

不在线第一只蜗牛

HarmonyOS 鸿蒙开发 HarmonyOS框架

为什么要将应用微服务化?

伤感汤姆布利柏

程序员 微服务 低代码 架构师 JNPF

即时通讯技术文集(第32期):IM开发综合技术合集(Part5) [共12篇]

JackJiang

网络编程 即时通讯 IM

MES生产执行系统在生产车间的主要作用

万界星空科技

工业互联网 MES系统 生产管理系统 mes 数字化车间

Meta Llama大模型:引领人工智能创新

百度开发者中心

人工智能 深度学习 大模型

如何写好大模型提示词?来自大赛冠军的经验分享(基础篇)

Baihai IDP

人工智能 AI LLM Prompt Prompt Engineering

每日一题:LeetCode-209. 长度最小的子数组

半亩房顶

算法 LeetCode 二分查找 前缀和 滑动窗口

如何在苹果手机上进行文件管理

大白话—70个你必须知道的AI重要概念(三)

Geek_2305a8

测试管理的核心知识点:岗位与职责全面解析

测吧(北京)科技有限公司

测试

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