vue2源码解析(三) —— $mount

600 阅读2分钟

微信公众号:  [大前端驿站]
关注大前端驿站。问题或建议,欢迎公众号留言。 这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

我们在上一章vue2源码解析(二)分析了new Vue之后的初始化实例各种属性的过程,今天接着上文继续讲解$mount,我们可以在源代码文件中看到好像几个地方都定义了$mount方法,这是因为挂载vm的这个过程和平台与构建方式有关系,我们可以看到有web和weex两个平台相关的挂载方式,这里我们只讲web平台相关的代码,我们先分析编译阶段的代码,也就是compile版本。

$mount

platforms/web/entry-runtime-with-compiler.js

主要作用:扩展默认的$mount方法,处理template或者el选项

...
const mount = Vue.prototype.$mount // 缓存runtime-only版本的$mounted
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  // 确保vue实例没有定义在html或者body标签上
  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
  // 判断选项中是否有render方法,有则直接调用mount方法,
  // 如果没有render,则需要调用compileToFunctions生成render再调用mount方法
  if (!options.render) {
    let template = options.template
    // 选项中有templat,根据特点进行不同判断处理
    if (template) {
      ...
    }else if (el) { // 有el,通过获取到外层html创建template
      template = getOuterHTML(el)
    }
    ...
    // 调用compileToFunctions生成render
    const { render, staticRenderFns } = compileToFunctions(...)
    options.render = render
    options.staticRenderFns = staticRenderFns
  }
  return mount.call(this, el, hydrating) // 执行runtime-only版本的$mounted
}

vue源码流程中只认识render函数,所以如果我们手动写render函数,那么就直接调用mount.call。反之,vue会将template做为参数,运行时调用compileToFunctions方法,转化为render函数,再去调用mount.call方法。 这里我们得出选项中部分配置的优先级:render > template > el

然后执行到mount变量缓存的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)
}

$mount调用了mountComponent函数 - src\core\instance\lifecycle.js

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 如果此时还是没有render方法,那就要抛出错误提示
  if (!vm.$options.render) {
    ...
  }
  callHook(vm, 'beforeMount')

  // 构建updateComponent方法,更新组件需要用到,很重要
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      ...
    }
  } else {
    updateComponent = () => {
      // 执行更新函数
      vm._update(vm._render(), hydrating)
    }
  }

  // new 一个监视器对象Watcher,将构建好的更新函数updateComponent作为传参
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // 手动挂载实例
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

首先判断render函数是否已经构建好,如果没有则报错。render没有问题则开始构建updateComponent方法,这个方法是每次更新组件时执行的方法。然后new Watch是Vue响应式处理中的依赖收集过程,其原理采用了观察者模式,后续我们会细讲这块响应式原理,这里暂且将他理解为监听变化之后需要执行updateComponent方法来更新组件。最后手动挂载实例。

上一章中我们创建过一个 inint.html 的例子,我们可以在$mount出打上一个debugger然后在浏览器中刷新init.html看看$mount的全部流程。

$mount在编译过程中构建 render的过程

mount01.png

这个例子中我们设置了 el 选项将会根据 getOuterHTML 方法拿到外层的父元素先得到 template,然后 compileToFunctions 得到 render

mount02.png

在runtime-only的 $mount中构建 updateComponent更新函数

mount03.png

首次挂载时vm.$vnode不存在,需要手动挂载实例

mount04.png

点击触发onclick事件时直接触发 updateComponent 方法,而不会再次触发 $mount方法

mount05.png


~~感谢观看

关注下方【大前端驿站】
让我们一起学,一起进步

【分享、点赞、在看】三连吧,让更多的人加入我们~~