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

1、前言

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

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

2、VD 是什么

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

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

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

<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])三个属性来描述。git

3、为何须要 VD

借助 VD,能够达到有效减小页面渲染次数的目的,从而提升渲染效率。咱们先来看下页面的更新通常会通过几个阶段。github

从上面的例子中,能够看出页面的呈现会分如下3个阶段:算法

  • JS计算
  • 生成渲染树
  • 绘制页面

这个例子里面,JS 计算用了691毫秒,生成渲染树578毫秒,绘制73毫秒。若是能有效的减小生成渲染树和绘制所花的时间,更新页面的效率也会随之提升。 经过 VD 的比较,咱们能够将多个操做合并成一个批量的操做,从而减小 dom 重排的次数,进而缩短了生成渲染树和绘制所花的时间。至于如何基于 VD 更有效率的更新 dom,是一个颇有趣的话题,往后有机会将另写一篇文章介绍。数组

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

咱们先从如何生成 VD 提及。借助 JSX 编译器,能够将文件中的 HTML 转化成函数的形式,而后再利用这个函数生成 VD。看下面这个例子:前端框架

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

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

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元素就建立完并展现到页面上了(页面比较丑,不要介意...)。

5、总结

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

P.S.: 想看完整代码见这里:代码

参考连接:

相关文章
相关标签/搜索