Moon系列之虚拟DOM相关

前言

虚拟DOM是Moon重要的部分,也是现在很多前端框架中都存在的部分,本文主要介绍虚拟DOM的概念以及它在Moon中具体表现形式。

具体分析

还是采用简单实例开始来分析整个虚拟DOM的创建以及与实际DOM之间的处理,实例代码如下:

  <div id="app">
    <p>{{today}}</p>
    <p>{{getToday}}</p>
    <mn-test></mn-test>
  </div>
  <script src="./moon.js"></script>
  <script>
    debugger
    Moon.component('mn-test', {
      template: '<p>{{msg}}</p>',
      data: {
        msg: 'hello world'
      }
    });
    new Moon({
      el: '#app',
      data: {
        today: Date.now()
      },
      computed: {
        getToday: {
          get() {
            var now = new Date(this.get('today'));
            return now.toLocaleString();
          }
        }
      }
    });
  </script>

上面的实例中涉及到三个部分:普通变量、计算属性、自定义模块,我们来看看Moon是如何处理的以及对应的虚拟DOM的生成。
之前通过对于Moon构造函数逻辑处理流程的分析得到如下几点:

  1. 在mount方法中获取this.$render方法,该方法就是用于产生虚拟DOM
  2. 在build方法中调用了render函数,产生了虚拟DOM
  3. 在patch方法中处理了虚拟DOM与浏览器DOM之间的转换

所以就下来就按照上面的逻辑处理来分析整个的处理流程。

构建this.$render方法
this.$render = Moon.compile(this.$template);

可见调用了Moon.compile方法,该方法顾名思义即编译的功能,具体看看该方法内部的具体处理流程。

    Moon.compile = function (template) {
      return compile(template);
    };

调用了私有方法compile

    var compile = function (template) {
      var tokens = lex(template);
      var ast = parse(tokens);
      return generate(ast);
    };

实际上lex函数是分析html中每一个标识符,parse是将所有的标识符构建成指定的树结构,generate是返回创建虚拟DOM的函数。

lex
lex部分整体处理
上面是lex部分整体的处理流程,具体的处理代码我会详细注释,此处就不将代码展示出来。
lex实际上就是分别对应处理html中的节点、标签、注释,构建成自己想要的对象,就上面的实例而言,html部分如下:

  <div id="app">
    <p>{{today}}</p>
    <p>{{getToday}}</p>
    <mn-test></mn-test>
  </div>

那么lex处理的输出结果是一个数组,数组中的元素都是对象,对象按照node类型分为标签对象、文本对象,具体组成属性如下:

    // 标签对象: attributes是标签属性
    // closeStart表示是否是闭合标签
    // type表示类型、value表示标签名称
    attributes: Object
    closeStart: true
    type: "tag"
    value: "div"
    // 文本对象
    // type表示类型,value表示文本值
    type: "text"
    value: "↵ "

parse

  var parse = function (tokens) {
      var root = {
        type: "ROOT",
        children: []
      };
      var state = {
        current: 0,
        tokens: tokens
      };
      // 遍历处理tokens中的标签
      while (state.current < tokens.length) {
        // 主要的处理就是walk函数,该函数是用于构建成虚拟dom树
        var child = walk(state);
        if (child) {
          root.children.push(child);
        }
      }
      return root;
    };

walk的处理代码会详细注释之后会上传到我的Github上,此处就不在粘贴出来,parse函数是将tokens中所有的node对象构建成树结构,该函数返回的结果结构为:

type: 'ROOT',
children: [
    {
        type: 标签名称
        props: 属性以及指令集合
        meta: 元数据
        children:子标签对象
    }
]

generate

generate处理逻辑
generate函数是依据虚拟DOM树创建function,function如下:

(function(h) {
    var instance = this;
    return h("div", {attrs: {"id": "app"}}, {
        "shouldRender": true,
        "eventListeners": {}
    }, [
    h("#text", {"shouldRender": false,"eventListeners": {}}, "\n "), 
    h("p", {attrs: {}}, {"shouldRender": true, "eventListeners": {}}, [h("#text", {"shouldRender": true,"eventListeners": {}}, "" + instance.get("today") + "")]), 
    h("#text", {"shouldRender": false,"eventListeners": {}}, "\n "),
    h("p", {attrs: {}}, {"shouldRender": true, "eventListeners": {}}, [h("#text", {"shouldRender": true,"eventListeners": {}}, "" + instance.get("getToday") + "")]), 
    h("#text", {"shouldRender": false, "eventListeners": {}}, "\n "), 
    h("mn-test", {attrs: {}}, {"shouldRender": false,"eventListeners": {}}, []), 
    h("#text", {"shouldRender": false,"eventListeners": {}}, "\n ")
        ])
 })

由上面构建的形式是将每个虚拟DOM对象都传递给h函数来处理,传递给h函数的参数分为两类,标签和文本,标签的话(标签名称,属性集,meta元数据,children),文本的话(#text,meta,值)。

经过上面步骤的处理:this.$render = (function(h) {代码});

build中调用render
    Moon.prototype.render = function () {
      return this.$render(h);
    };

核心的处理就是h函数,h函数是Moon内部的私有方法,具体处理逻辑如下:

  var h = function (tag, attrs, meta, children) {
      var component = null;
      // #text的处理
      if (tag === TEXT_TYPE) {
        return createElement(TEXT_TYPE, meta, { attrs: {} }, attrs, []);
      } else if ((component = components[tag]) !== undefined) {
        // 自定义组件的处理
        if (component.opts.functional === true) {
          return createFunctionalComponent(attrs, children, components[tag]);
        } else {
          meta.component = component;
        }
      }
      // 组件或者标签处理
      return createElement(tag, "", attrs, meta, children);
    };

createElement的实际处理如下:

   var createElement = function (type, val, props, meta, children) {
      return {
        type: type,
        val: val,
        props: props,
        children: children,
        meta: meta || defaultMetadata()
      };
    };

可见build中render就是执行h函数,构建虚拟DOM树

patch

该方法是将虚拟DOM中的改变实际应用到浏览器DOM中,这部分的处理逻辑相对复杂些,整个的处理逻辑如下:
整体处理逻辑

总结

Moon整个虚拟DOM的逻辑处理大致如此,更多细节会在源码中详细注释,通过分析Moon了解对入虚拟DOM有了直观的认知:所谓的虚拟DOM实际上就是模拟浏览器DOM的功能,减少实际修改DOM带来的浏览器重绘相关的代价,本质上它们还是一些具有DOM信息的对象。

猜你喜欢

转载自blog.csdn.net/s1879046/article/details/79083222