你不知道的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 对象例子:

  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. Hello World
    • 第1

一个 dom 对象,比如 li,由 tag(li), props({id:1,class:"li-1"})children(["第",1])三个属性来描述。

三、为什么需要 VD

借助 VD,可以达到有效减少页面渲染次数的目的,从而提高渲染效率。我们先来看下页面的更新一般会经过几个阶段:

你不知道的Virtual DOM(一):Virtual Dom介绍_第1张图片

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

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

这个例子里面,JS 计算用了 691毫秒,生成渲染树 578毫秒,绘制 73毫秒。如果能有效的减少生成渲染树和绘制所花的时间,更新页面的效率也会随之提高。

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

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

我们先从如何生成 VD 说起。借助 JSX 编译器,可以将文件中的 HTML 转化成函数的形式,然后再利用这个函数生成 VD。看下面这个例子:

  1. function render() {
  2. return (
  3. Hello World
    • 第1
  • );
  • }
  • 这个函数经过 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 (
      • 0
      • {
      • [1, 2, 3].map( i => (
      • {i}
      • ))
      • }
    3. );
    4. }
    5. // JSX编译后
    6. function render() {
    7. return h(
    8. 'ul',
    9. null,
    10. h(
    11. 'li',
    12. null,
    13. '0'
    14. ),
    15. /*
    16. * 需要将下面这个数组解构出来再放到children数组中
    17. */
    18. [1, 2, 3].map(i => h(
    19. 'li',
    20. null,
    21. i
    22. ))
    23. );
    24. }

    继续之前的例子。执行 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元素就创建完并展示到页面上了(页面比较丑,不要介意...)。

    你不知道的Virtual DOM(一):Virtual Dom介绍_第2张图片

    五、总结

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

    PS: 想看完整代码见这里:

    代码(https://gist.github.com/dicke...

    参考链接:

    -The End-
    来自微信公众号:有赞coder

    有赞云开发者公众号整理了全面的技术合集大礼包,类似文章应有尽有,涵盖前端、移动端、中间件、后端、架构框架、运维、大数据、测试、以及各种技术视频……
    关注“有赞云开发者”公众号,回复“技术”即可获取!

    你可能感兴趣的:(react.js,vue.js)