vue源码2----$mount挂载

vue中是通过$mount函数来挂载vm的,$mount和平台、构建方式都有关系

以下是对compiler版本的分析

首先函数定义在src/platform/web/entry-runtime-with-compiler.js中

const mount = Vue.prototype.$mount //这里缓存了runtime-only版本的$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el) //很简单的dom查询,通过querySelector

  /* istanbul ignore if */ 
  if (el === document.body || el === document.documentElement) { //确保vue实例没有定义在html或者body标签上
    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) { //判断是否具有render函数,没有则通过template或者el构建render函数
    let template = options.template
    if (template) {//通过template构建render
      if (typeof template === 'string') {//template传入的是字符串
        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) {//传来的是dom节点
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {//通过el构建
      template = getOuterHTML(el) //获取到外层html
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, { //通过调用compileToFunctions生成render函数
        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)//调用runtime-only版本的$mount实现挂载
}

1.缓存runtime-only版本的$mount,具体定义在src/platform/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中

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el //缓存el
  if (!vm.$options.render) {//如果到这里还没有生成render函数要报错
    vm.$options.render = createEmptyVNode //创建一个空的vnode
    if (process.env.NODE_ENV !== 'production') {
      /* 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')

  let updateComponent //定义updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}` //性能相关
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render() //调用一次render函数,生成虚拟dom
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating) //调用_update。执行一次实际的渲染
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      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, { //这里新建了渲染watcher,将定义的updateComponent传入
    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
}

2.通过query函数查找el。query定义在src/platform/web/util/index.js

export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)//通过querySelector方法查找el
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')//没找到也要返回一个div
    }
    return selected
  } else {
    return el
  }
}

3.对el进行一个判断,确保el不是body或者html节点

4.通过el或template生成render函数

5.调用runtime-only版本的$mount挂载

 

posted @ 2020-04-20 16:49  我喝牛奶不舔盖  阅读(495)  评论(0)    收藏  举报
I hear and I forget. I see and I remember. I do and I understand