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

2018 年 10 月 14 日 前端大全

(点击上方公众号,可快速关注)


作者:范明非

https://fanmingfei.com/posts/Animation_Base.html


前言

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

这是VD系列文章的开篇,以下是本系列其它文章的传送门:

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

  • 你不知道的Virtual DOM(二):Virtual Dom的更新

  • 你不知道的Virtual DOM(三):Virtual Dom更新优化

  • 你不知道的Virtual DOM(四):key的作用

  • 你不知道的Virtual DOM(五):自定义组件

  • 你不知道的Virtual DOM(六):事件处理&异步更新

PS:系列文章可点击“阅读全文”查看。

VD是什么

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

  
  
    
  1. {

  2.    tag: "div",

  3.    props: {},

  4.    children: [

  5.        "Hello World",

  6.        {

  7.            tag: "ul",

  8.            props: {},

  9.            children: [{

  10.                tag: "li",

  11.                props: {

  12.                    id: 1,

  13.                    class: "li-1"

  14.                },

  15.                children: ["第", 1]

  16.            }]

  17.        }

  18.    ]

  19. }

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

  
  
    
  1. <div>

  2.    Hello World

  3.    <ul>

  4.        <li id="1" class="li-1">

  5.            第1

  6.        </li>

  7.    </ul>

  8. </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。看下面这个例子:

  
  
    
  1. function render() {

  2.    return (

  3.        <div>

  4.            Hello World

  5.            <ul>

  6.                <li id="1" class="li-1">

  7.                    第1

  8.                </li>

  9.            </ul>

  10.        </div>

  11.    );

  12. }

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

  
  
    
  1. function render() {

  2.    return h(

  3.        'div',

  4.        null,

  5.        'Hello World',

  6.        h(

  7.            'ul',

  8.            null,

  9.            h(

  10.                'li',

  11.                { id: '1', 'class': 'li-1' },

  12.                '\u7B2C1'

  13.            )

  14.        )

  15.    );

  16. }

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

  
  
    
  1. // .babelrc文件

  2. {

  3.  "plugins": [

  4.    ["transform-react-jsx", {

  5.      "pragma": "h"    // 这里可配置任意的名称

  6.    }]

  7.  ]

  8. }

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

  
  
    
  1. function flatten(arr) {

  2.    return [].concat.apply([], arr);

  3. }

  4. function h(tag, props, ...children) {

  5.    return {

  6.        tag,

  7.        props: props || {},

  8.        children: flatten(children) || []

  9.    };

  10. }

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

  
  
    
  1. function render() {

  2.    return (

  3.        <ul>

  4.            <li>0</li>

  5.            {

  6.                [1, 2, 3].map( i => (

  7.                    <li>{i}</li>

  8.                ))

  9.            }

  10.        </ul>

  11.    );

  12. }

  13. // JSX编译后

  14. function render() {

  15.    return h(

  16.        'ul',

  17.        null,

  18.        h(

  19.            'li',

  20.            null,

  21.            '0'

  22.        ),

  23.        /*

  24.         * 需要将下面这个数组解构出来再放到children数组中

  25.         */

  26.        [1, 2, 3].map(i => h(

  27.            'li',

  28.            null,

  29.            i

  30.        ))

  31.    );

  32. }

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

  
  
    
  1. {

  2.    tag: "div",

  3.    props: {},

  4.    children: [

  5.        "Hello World",

  6.        {

  7.            tag: "ul",

  8.            props: {},

  9.            children: [{

  10.                tag: "li",

  11.                props: {

  12.                    id: 1,

  13.                    class: "li-1"

  14.                },

  15.                children: ["第", 1]

  16.            }]

  17.        }

  18.    ]

  19. }

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

  
  
    
  1. // 创建dom元素

  2. function createElement(vdom) {

  3.    // 如果vdom是字符串或者数字类型,则创建文本节点,比如“Hello World”

  4.    if (typeof vdom === 'string' || typeof vdom === 'number') {

  5.        return doc.createTextNode(vdom);

  6.    }

  7.    const {tag, props, children} = vdom;

  8.    // 1. 创建元素

  9.    const element = doc.createElement(tag);

  10.    // 2. 属性赋值

  11.    setProps(element, props);

  12.    // 3. 创建子元素

  13.    // appendChild在执行的时候,会检查当前的this是不是dom对象,因此要bind一下

  14.    children.map(createElement)

  15.            .forEach(element.appendChild.bind(element));

  16.    return element;

  17. }

  18. // 属性赋值

  19. function setProps(element, props) {

  20.    for (let key in props) {

  21.        element.setAttribute(key, props[key]);

  22.    }

  23. }

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

总结

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


【关于投稿】


如果大家有原创好文投稿,请直接给公号发送留言。


① 留言格式:
【投稿】+《 文章标题》+ 文章链接

② 示例:
【投稿】《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/

③ 最后请附上您的个人简介哈~



觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

登录查看更多
0

相关内容

Python分布式计算,171页pdf,Distributed Computing with Python
专知会员服务
107+阅读 · 2020年5月3日
专知会员服务
61+阅读 · 2020年3月4日
MIT新书《强化学习与最优控制》
专知会员服务
275+阅读 · 2019年10月9日
【初学者系列】tensorboard学习笔记
专知
7+阅读 · 2019年10月4日
Cayley图数据库的可视化(Visualize)
Python开发者
5+阅读 · 2019年9月9日
文本+视觉,多篇 Visual/Video BERT 论文介绍
AI科技评论
22+阅读 · 2019年8月30日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
文本分析与可视化
Python程序员
9+阅读 · 2019年2月28日
迁移学习之Domain Adaptation
全球人工智能
18+阅读 · 2018年4月11日
一张通往计算机世界的地图
中科院物理所
8+阅读 · 2017年10月12日
使用 Python 绘制《星战》词云
Datartisan数据工匠
3+阅读 · 2017年8月31日
Video-to-Video Synthesis
Arxiv
9+阅读 · 2018年8月20日
Arxiv
9+阅读 · 2016年10月27日
Arxiv
3+阅读 · 2012年11月20日
VIP会员
相关资讯
【初学者系列】tensorboard学习笔记
专知
7+阅读 · 2019年10月4日
Cayley图数据库的可视化(Visualize)
Python开发者
5+阅读 · 2019年9月9日
文本+视觉,多篇 Visual/Video BERT 论文介绍
AI科技评论
22+阅读 · 2019年8月30日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
文本分析与可视化
Python程序员
9+阅读 · 2019年2月28日
迁移学习之Domain Adaptation
全球人工智能
18+阅读 · 2018年4月11日
一张通往计算机世界的地图
中科院物理所
8+阅读 · 2017年10月12日
使用 Python 绘制《星战》词云
Datartisan数据工匠
3+阅读 · 2017年8月31日
相关论文
Top
微信扫码咨询专知VIP会员