Vue.js源码学习——mount实例的挂载

298 阅读1分钟

解析:

runtime-with-compiler版本入口: src/platforms/web/entry-runtime-with-compiler.js

在这个文件中可以看到在Vue的原型上重新定义了$mount方法。在重新定义之前,可以看到Vue的原型上是已经存在了$mount这个方法的,重新定义的目的就是区别于runtime-only版本,在这之前需要compiler

/* src/platforms/web/entry-runtime-with-compiler.js */
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  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
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          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) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      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

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

el = el && query(el)这行是根据传入的eldocument中查找元素,本质就是调用了document.querySelector。el元素也是有限制的,它不能是bodyhtml元素,因为在进行挂载的时候,会整个的进行替换

/* src/platforms/web/util/index.js */
export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')
    }
    return selected
  } else {
    return el
  }
}

接下来就是去看options中有没有定义render函数,如果不存在则需要根据template去编译从而生成一个render函数。实质上Vue最后的渲染就只依赖于render函数。中间的一大段过程就是在判断template的合法性然后将template编译成render函数。这个部分就是比runtime-only版本多出来的complier部分。最后调用mount.call(this, el, hydrating),该mount方法就是runtime-only版本中使用的Vue.prototype.$mount方法。

回到runtime-only版本的Vue.prototype.$mount方法

/* src/platforms/web/runtime/index.js */
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

最后调用了mountComponent这个方法。这个方法定义在src/core/instance/lifecycle.js

mountComponent方法一开始就会判断render函数是否存在,在非生产环境的时候就会进行报错提示。这个方法中定义了一个Watcher对象,是一个renderWatcher

/* src/core/instance/lifecycle.js */
new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

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

查看watcher的构造函数,updateComponent是作为expOrFn(string | Function)参数传递进入的,当该参数是一个函数的时候,会把该函数赋值给watchergetter属性。当数据变更的时候,触发视图更新。

updateComponent实质上先调用了vm._render()来获取vnode,然后调用vm._update()来将虚拟dom真实dom进行挂钩。

挂载流程: