写于vue3.0发布前夕的helloworld之二

接着,继续走,来到了vm.$mount。

开始生成render函数,生成VNode,由于是第一次加载,所以patch机制为只删除前一个dom节点机制,下面都会讲到。

先到$mount:

Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
      warn(
        "Do not mount Vue to <html> or <body> - mount to normal elements instead."
      );
      return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    if (!options.render) {
      var template = options.template;
      if (template) {
        if (typeof template === 'string') {
          if (template.charAt(0) === '#') {
            template = idToTemplate(template);
            /* istanbul ignore if */
            if (!template) {
              warn(
                ("Template element not found or is empty: " + (options.template)),
                this
              );
            }
          }
        } else if (template.nodeType) {
          template = template.innerHTML;
        } else {
          {
            warn('invalid template option:' + template, this);
          }
          return this
        }
      } else if (el) {
        template = getOuterHTML(el);
      }
      if (template) {
        /* istanbul ignore if */
        if (config.performance && mark) {
          mark('compile');
        }

        var ref = compileToFunctions(template, {
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines,
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this);
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns;
        options.render = render;
        options.staticRenderFns = staticRenderFns;

        /* istanbul ignore if */
        if (config.performance && mark) {
          mark('compile end');
          measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
        }
      }
    }
    return mount.call(this, el, hydrating)
  };

一句句来讲,这里边是挺长,但是足够简单,首先判断el是否为body或者html元素,是则警告,然后退出当前函数,然后根据条件拿到template,因为我们没有设置render和template,所以template走else分支getOuterHTML,拿到template之后,会将当前的template编译成一个render函数,这个编译函数叫做compileToFunctions:这个函数是真的长,而且与主题干系不大,这里我们用文字解释一下就行,首先他是一个闭包函数,闭包了一个叫cache的对象这个对象是缓存当前已经编译过的模板的,进入函数之后有缓存直接返回缓存,没有的话就会执行compile函数,这些都是编译过程,最终compile会调用parseHTML方法使用正则加字符串分析字符串,返回一个这样的对象:

{   
    "type":1,
    "tag":"div",
    "attrsList":[
        {"name":"id","value":"app","start":5,"end":13}
    ],
    "attrsMap":{
        "id":"app"
    },
    "rawAttrsMap":{
        "id":{
            "name":"id",
            "value":"app",
            "start":5,
            "end":13
            }
    },
    "children":
    [
        {
            "type":2,
            "expression":"_s(msg)",
            "tokens":[{"@binding":"msg"}],
            "text":"{{msg}}",
            "start":14,"end":21
        }
    ],
    "start":0,
    "end":27,
    "plain":false,
    "attrs":[{"name":"id","value":"\"app\"","start":5,"end":13}]
}

这就是我们的ast语法树,之后会根据ast生成code,这个方法叫做generate:

function generate (
    ast,
    options
  ) {
    var state = new CodegenState(options);
    var code = ast ? genElement(ast, state) : '_c("div")';
    return {
      render: ("with(this){return " + code + "}"),
      staticRenderFns: state.staticRenderFns
    }
  }

这个方法调用了genElement方法,简而言之,里边的方法生成code时都会以ast上的值为原料,进行字符串拼接,最后code长这样:

_c('div',{attrs:{"id":"app"}},[_v(_s(msg))])

这里做一下解释_c,_v都是vue内部创建虚拟dom的函数,_s其实就是将当前值转化成字符串,继续走,generate执行完毕之后返回到compileToFunctions,这个时候拼接好的render字符串是这样:

"with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(msg))])}"

然后会使用new Function 将其生成一个函数,这个函数就是我们的render,组建上的render,执行完compileToFunctions,继续回到$mount函数里,执行最终的$mount:

Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };

可见mout调用了mountComponent方法:

function mountComponent (
    vm,
    el,
    hydrating
  ) {
    vm.$el = el;
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode;
      {
        /* istanbul ignore if */
        if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
          vm.$options.el || el) {
          warn(
            'You are using the runtime-only build of Vue where the template ' +
            'compiler is not available. Either pre-compile the templates into ' +
            'render functions, or use the compiler-included build.',
            vm
          );
        } else {
          warn(
            'Failed to mount component: template or render function not defined.',
            vm
          );
        }
      }
    }
    callHook(vm, 'beforeMount');

    var updateComponent;
    /* istanbul ignore if */
    if (config.performance && mark) {
      updateComponent = function () {
        var name = vm._name;
        var id = vm._uid;
        var startTag = "vue-perf-start:" + id;
        var endTag = "vue-perf-end:" + id;

        mark(startTag);
        var vnode = vm._render();
        mark(endTag);
        measure(("vue " + name + " render"), startTag, endTag);

        mark(startTag);
        vm._update(vnode, hydrating);
        mark(endTag);
        measure(("vue " + name + " patch"), startTag, endTag);
      };
    } else {
      updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };
    }

    // we set this to vm._watcher inside the watcher's constructor
    // since the watcher's initial patch may call $forceUpdate (e.g. inside child
    // component's mounted hook), which relies on vm._watcher being already defined
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
    hydrating = false;

    // manually mounted instance, call mounted on self
    // mounted is called for render-created child components in its inserted hook
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
  }

mountComponent方法一进来,先判断是否有render,之后会根据条件警告一些信息。然后开始调起beforeMount钩子函数,下来初始化一个updateComponent方法,他的正常版本长这样:

updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };

实际上在组建的dom更新,包括第一次挂载,和随后的相应式更新上,本质都用的是这一个函数。然后继续走,重头戏来啦,watcher在这里开始初始化,注意传给他的第四个参数,在哪里我们把要调用beforeUpdate给传了过去。
我们的下面来到watcher的构造函数:

接下

posted @ 2019-09-25 20:00  子龙_子龙  阅读(12)  评论(0编辑  收藏  举报