Skip to content

源码笔记(五):beforeMount 阶段

接上文,在触发生命周期钩子 created 后,执行:

js
if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}

该句执行完后,_init 方法(位置在 src/core/instance/init.js)执行结束。

$mount 简述

判断是否有 $options.el,如果有就直接执行:vm.$mount(vm.$options.el)。在前面已经提到 $mount 方法与平台相关,所以在本例会执行 entry-runtime-with-compiler.js 中的 $mount

$mount 的主要功能是根据 el 或者 options 里的 template,通过编译器编译成 renderstaticRenderFns

构建 render

$mount 里先取得 el 对应的 dom 节点,然后判断 options(即 this.$options) 里有没有 render 方法,有 render 就可以直接跳过取 template 的过程了;没有 render 则判断 options 有没有 template,没有提供 template 就用 getOuterHTML(el) 得一个 template

compileToFunctions

得到 template 后,执行:

js
var ref = compileToFunctions(
  template,
  {
    outputSourceRange: process.env.NODE_ENV !== 'production',
    shouldDecodeNewlines: shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments,
  },
  this
);

compileToFunctions 是柯里化函数 createCompileToFunctionFn(compile) 的返回值,主要作用是将 template 转化为 render 函数。

内部先执行 new Function('return 1') 来判断 csp 的配置,然后根据(options.delimiters)是否存在设置 key, 判断缓存后执行:

compile

js
var compiled = compile(template, options);

compilecreateCompiler 里定义,方法里先处理合并处理了 compileToFunctions 传入的 optionsbaseOptions(来自平台相关) 相关配置(定义了 warn,合并了 modulesdirectives)得到 finalOptions,然后执行:

baseCompile
js
var compiled = baseCompile(template.trim(), finalOptions);

baseCompile 方法里执行 3 个步骤:

  • parse 模板解析为 ast

  • optimize 标记静态节点,分别递归调用 markStatic$1markStaticRoots 方法得到静态节点标志挂载在 ast 对象下的的 staticstaticRoot 属性。其中 static代表该节点是普通静态节点,staticRoot 代表是可以优化的静态节点,他来自于:

    js
    if (node.static && node.children.length && !(node.children.length === 1 && node.children[0].type === 3)) {
      node.staticRoot = true;
      return;
    } else {
      node.staticRoot = false;
    }

    表明如果 node.static 为真(即静态节点)且他不仅仅只有一个文本子元素为的时候 node.staticRoot 就为真,即用来优化。如果只有一个子文本元素就无需优化,优化反而增加成本。

  • generateAST 转换成渲染函数,其中如果有标记 node.staticRoot 为真,则将 staticRenderFns 数组增加一个静态的 render

最终 baseCompile 返回:

js
return {
  ast: ast,
  render: code.render,
  staticRenderFns: code.staticRenderFns,
};

继而 compile 返回:

js
return {
  ast: {type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, rawAttrsMap: {…}, …}
  render: "with(this){return _c('div',{attrs:{"id":"main"}..."
  staticRenderFns: []
  errors: []
  tips: []
};

处理编译错误后,对 renderstaticRenderFns 进行函数包装( createFunction )并缓存结果,这样在后续如果再次解析到相同的模板可以直接读缓存。

继而 compileToFunctions 返回:

js
return {
  render: () => {
    with (this) {
      return 'xxx';
    }
  },
  staticRenderFns: [],
};

到此 template->render 的编译过程结束。

执行 runtime 里的原 $mount 及 mountComponent

回到 vm.$mount 方法里继续执行,将 renderstaticRenderFns 赋到 vm.$options 上后,执行:

js
return mount.call(this, el, hydrating);

即执行被覆盖的之前的 $mount (/runtime/index 中定义),内部执行:

js
mountComponent(this, el, hydrating);

mountComponent (core/instance/lifecycle 中定义)里先将原始的真实 el 赋给 vm.$el,判断 $options.render 是否存在进行相关报错处理。

触发 beforeMount 钩子

js
callHook(vm, 'beforeMount');

执行生命周期钩子 beforeMount,打印 vue beforeMount

本章小结

  1. 本章介绍了 vue 执行的 beforeMount 阶段;
  2. 该阶段主要执行与平台相关的 $mount,主要生成 render 函数;
  3. $mount经过 compileToFunctions -> compile -> baseCompile 得到 render,其中 baseCompile 的执行会经过 3 个阶段;
  4. $mount 最后执行原 $mount(平台相关),方法里执行 mountComponent 开始进入构建 vnode 阶段。