美文网首页
手写虚拟DOM(一)—— VirtualDOM介绍

手写虚拟DOM(一)—— VirtualDOM介绍

作者: 青叶小小 | 来源:发表于2020-12-25 10:45 被阅读0次

本文为系列文章:

手写虚拟DOM(一)—— VirtualDOM介绍
手写虚拟DOM(二)—— VirtualDOM Diff
手写虚拟DOM(三)—— Diff算法优化
手写虚拟DOM(四)—— 进一步提升Diff效率之关键字Key
手写虚拟DOM(五)—— 自定义组件
手写虚拟DOM(六)—— 事件处理
手写虚拟DOM(七)—— 异步更新

一、前言

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

二、Virtual DOM是什么?

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

下面是一个典型的Virtual DOM对象例子:

{
    "tag": "div",
    "props":{},
    "children": [
        "Hello World!",
        {
            "tag": "div",
            "props":
            {
                "id": "div1",
                "data-idx": 1
            },
            "children": ["first"]
        },
        {
            "tag": "div",
            "props":
            {
                "id": "div2"
            },
            "children": ["second"]
        }
    ]
}

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

<div>
    Hello World!
    <div id="div1" data-idx={1}>first</div>
    <div id="div2">second</div>
</div>

一个dom,是由tag(div), props({id: "div2"}), children(["second"]) 组成;

三、为什么需要Virtual DOM

Virtual DOM 最大的特点是将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。
如 React 就借助 Virtual DOM 实现了服务端渲染、浏览器渲染和移动端渲染等功能。
此外,在进行页面更新的时候,借助Virtual DOM,DOM 元素的改变可以在内存中进行比较,
再结合框架的事务机制将多次比较的结果合并后一次性更新到页面,从而有效地减少页面渲染的次数,提高渲染效率。
我们先来看下页面的更新一般会经过几个阶段:


image.png

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

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

这个例子里面,JS计算用了935毫秒,生成渲染树143毫秒,绘制60毫秒。

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

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

我们先从如何生成Virtual DOM说起。

借助JSX编译器,可以将文件中的HTML转化成函数的形式,然后再利用这个函数生成Virtual DOM。看下面这个例子:

function render() {
    return (
        <div>
            Hello World!
            <div id="div1" data-idx={1}>first</div>
            <div id="div2">second</div>
        </div>
    );
}

通过babel的plugin(transform-react-jsx)编译后,可以生成如下代码:

function render() {
    return v(
        "div",
        null,
        "Hello World!",
        v(
            "div",
            { id: "div1", "data-idx": 1 },
            "first"
        ),
        v(
            "div",
            { id: "div2" },
            "second"
        )
    );
}

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

// .babelrc

// 安装npm包: yarn add babel-cli babel-plugin-transform-react-jsx
// 其中:
// babel-cli 是babel的命令行工具,需要将原始的 .js或.jsx 文件编译
{
  "plugins": [
    [
      "transform-react-jsx",
      {
        "pragma": "v"
      }
    ]
  ]
}

接下来,我们只需要定义v函数,就能构造出Virtual DOM:

function flatten(children) {
    return [].concat.apply([], children);
}

function v(tag, props, ...children) {
    return {
        tag,
        props: props || {},
        children: flatten(children) || []
    }
}

之后,我们要基于Virtual DOM,遍历并生成真实的DOM:

function setProps(element, props) {
    for (let k in props) {
        if (props.hasOwnProperty(k)) {
            element.setAttribute(k, props[k]);
        }
    }
}

function createElement(vdom) {
    const t = typeof vdom;
    if (t === 'string' || t === 'number') {
        return document.createTextNode(vdom);
    }

    const {tag, props, children} = vdom;

    // 1. 创建元素
    const element = document.createElement(tag);
    
    // 2. 属性赋值
    setProps(element, props);
    
    // 3. 创建子元素
    // appendChild在执行的时候,会检查当前的this是不是dom对象,因此要bind一下
    children.map(createElement).forEach(element.appendChild.bind(element));
    
    return element;
}

然后,我们将生成好的dom挂载到指定的节点上,就完成了:

function render(vdom, container) {
    container.appendChild(createElement(vdom));
}

// 执行render
render(
    view(),
    document.getElementById("app")
);

最后,我们需要一个html来显示我们的例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app"></div>
    <script src="bundle.js"></script>
</body>
</html>

展示效果如下:


image1.png

五、总结

本文介绍了Virtual DOM的基本概念,并讲解了如何利用JSX编译HTML标签,然后生成Virtual DOM,进而创建真实dom的过程。

项目源码:
https://github.com/qingye/VirtualDOM-Study/tree/master/VirtualDOM-Study-01

相关文章

网友评论

      本文标题:手写虚拟DOM(一)—— VirtualDOM介绍

      本文链接:https://www.haomeiwen.com/subject/ngcmnktx.html