Vue2源码,生命周期篇-模板编译阶段

375 阅读1分钟

各种初始化阶段完成之后,调用了vm.$mount

这个阶段的主要内容是,获取用户传入的模板内容,并将其编译成渲染函数

模板编译阶段并不是存在于Vue的所有构建版本中,它只存在于完整版(即vue.js)中。在只包含运行时版本(即vue.runtime.js)中并不存在该阶段,这是因为当使用vue-loadervueify时,*.vue文件内部的模板会在构建时预编译成渲染函数,所以是不需要编译的,从而不存在模板编译阶段

$mount

只包含运行时版本的$mount代码如下:

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

完整版本的$mount代码如下

var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el,hydrating) {
  // 省略获取模板及编译代码
  return mount.call(this, el, hydrating)
}

完整版的vm.$mount方法定义位于源码的src/platforms/web/entry-runtime-with-compiler.js

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  /* 调用query方法,也就是说,如果el是个字符串,那就document.querySelector, 
    否则就直接返回
  */
  el = el && query(el)
  /* istanbul ignore if */
  /* 不能直接挂载到body或者HTML上上 */
  /* 
    Vue模板中的内容将会替换el 对应的DOM元素
    如果是body或者HTML会破坏文档流
  */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
  const options = this.$options
  // resolve template/el and convert to render function
  /* 
    如果没有写render,就获取template,编译成render函数
  */
  if (!options.render) {
    let template = options.template
    if (template) {
      /* 如果写了template 那么走下面的 */
      /* 如果手写了,就用下面这个值, 如果没有手写,那么就用el 去获取 */
      if (typeof template === 'string') {
        /* 如果变量template存在,则接着判断如果template是字符串并且以##开头,
        则认为template是id选择符 */
        if (template.charAt(0) === '#') {
          /* 用idToTemplate函数获取到选择符对应的DOM元素的innerHTML作为模板 */
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        /* 判断是不是一个DOM元素,如果是的话,就返回innerHTML */
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      /* 
        如果没有template这个options
        那么就根据el ,获取外部模板
      */
      template = getOuterHTML(el)
    }

    if (template) {
      /* 上面模板已经准备好了,接下来,就是把模板编译成render函数 */
      /* 下面就是关键的编译了 */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      /* 主要在compileToFunctions这个函数中进行的
        返回render这个函数,
        然后挂载到options上面  
      */
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)

      options.render = render
      options.staticRenderFns = staticRenderFns
        
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  /* mount 就是runtime里面的 $mount */
  return mount.call(this, el, hydrating)
}