Vue中是通过$mount实例方法去挂载vm的,$mount方法在多个文件中都有定义

  • src/platform/web/entry-runtime-with-compiler.js
  • src/platform/web/runtime/index.js
  • src/platform/weex/runtime/index.js

$mount方法的实现和平台、构建方式都相关的
以带compiler版本的$mount实现为例,因为抛开webpack的vue-loader,在纯前端浏览器环境分析Vue的工作原理有助于对原理的深入理解

实现

src/platform/web/entry-runtime-with-compiler.js 文件中定义

  1. const mount = Vue.prototype.$mount // 缓存原型上的$mount方法
  2. // 重新定义$mount方法
  3. Vue.prototype.$mount = function (
  4. el?: string | Element,
  5. hydrating?: boolean
  6. ): Component {
  7. el = el && query(el)
  8. /* istanbul ignore if */
  9. // Vue不能挂载在body、html这样的根节点上
  10. if (el === document.body || el === document.documentElement) {
  11. process.env.NODE_ENV !== 'production' && warn(
  12. `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
  13. )
  14. return this
  15. }
  16. const options = this.$options
  17. // resolve template/el and convert to render function
  18. // 没有定义render方法
  19. if (!options.render) {
  20. let template = options.template
  21. if (template) {
  22. if (typeof template === 'string') {
  23. if (template.charAt(0) === '#') {
  24. template = idToTemplate(template)
  25. /* istanbul ignore if */
  26. if (process.env.NODE_ENV !== 'production' && !template) {
  27. warn(
  28. `Template element not found or is empty: ${options.template}`,
  29. this
  30. )
  31. }
  32. }
  33. } else if (template.nodeType) {
  34. template = template.innerHTML
  35. } else {
  36. if (process.env.NODE_ENV !== 'production') {
  37. warn('invalid template option:' + template, this)
  38. }
  39. return this
  40. }
  41. } else if (el) {
  42. template = getOuterHTML(el)
  43. }
  44. if (template) {
  45. /* istanbul ignore if */
  46. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  47. mark('compile')
  48. }
  49. // compileToFunctions方法 写了el或者template属性最终都会转换成render方法 "在线编译"
  50. const { render, staticRenderFns } = compileToFunctions(template, {
  51. outputSourceRange: process.env.NODE_ENV !== 'production',
  52. shouldDecodeNewlines,
  53. shouldDecodeNewlinesForHref,
  54. delimiters: options.delimiters,
  55. comments: options.comments
  56. }, this)
  57. options.render = render
  58. options.staticRenderFns = staticRenderFns
  59. /* istanbul ignore if */
  60. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  61. mark('compile end')
  62. measure(`vue ${this._name} compile`, 'compile', 'compile end')
  63. }
  64. }
  65. }
  66. // 调用原先原型上的$mount方法挂载
  67. return mount.call(this, el, hydrating)
  68. }

原先原型上的 $mount 方法

在 src/platform/web/runtime/index.js 中定义,之所以这么设计完全是为了复用,因为它是可以被runtime only版本的Vue直接使用的

  1. import { mountComponent } from 'core/instance/lifecycle'
  2. // public mount method
  3. // 公用的挂载方法
  4. Vue.prototype.$mount = function (
  5. el?: string | Element,
  6. hydrating?: boolean
  7. ): Component {
  8. el = el && inBrowser ? query(el) : undefined
  9. return mountComponent(this, el, hydrating)
  10. }

参数

  1. el - 挂载的元素,可以是字符串,也可以是DOM对象,如果是字符串在浏览器环境下会调用query方法转换成DOM对象的
  2. hydrating - 和服务端渲染相关,在浏览器环境下不需要传第二个参数

    mountComponent方法

    定义在 src/core/instance/lifecycle.js 文件中

    1. export function mountComponent (
    2. vm: Component,
    3. el: ?Element,
    4. hydrating?: boolean
    5. ): Component {
    6. vm.$el = el
    7. if (!vm.$options.render) {
    8. vm.$options.render = createEmptyVNode
    9. if (process.env.NODE_ENV !== 'production') {
    10. /* istanbul ignore if */
    11. if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
    12. vm.$options.el || el) {
    13. warn(
    14. 'You are using the runtime-only build of Vue where the template ' +
    15. 'compiler is not available. Either pre-compile the templates into ' +
    16. 'render functions, or use the compiler-included build.',
    17. vm
    18. )
    19. } else {
    20. warn(
    21. 'Failed to mount component: template or render function not defined.',
    22. vm
    23. )
    24. }
    25. }
    26. }
    27. callHook(vm, 'beforeMount')
    28. let updateComponent
    29. /* istanbul ignore if */
    30. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    31. updateComponent = () => {
    32. const name = vm._name
    33. const id = vm._uid
    34. const startTag = `vue-perf-start:${id}`
    35. const endTag = `vue-perf-end:${id}`
    36. mark(startTag)
    37. // 调用vm.render方法先生成虚拟Node
    38. const vnode = vm._render()
    39. mark(endTag)
    40. measure(`vue ${name} render`, startTag, endTag)
    41. mark(startTag)
    42. // 调用vm.update更新DOM
    43. vm._update(vnode, hydrating)
    44. mark(endTag)
    45. measure(`vue ${name} patch`, startTag, endTag)
    46. }
    47. } else {
    48. updateComponent = () => {
    49. vm._update(vm._render(), hydrating)
    50. }
    51. }
    52. // we set this to vm._watcher inside the watcher's constructor
    53. // since the watcher's initial patch may call $forceUpdate (e.g. inside child
    54. // component's mounted hook), which relies on vm._watcher being already defined
    55. // 核心:实例化一个渲染Watcher 回调函数中会调用updateComponent方法
    56. // Watcher作用
    57. // 一是初始化的时候会执行回调函数
    58. // 二是当vm实例中的监测的数据发生变化的时候执行回调函数
    59. new Watcher(vm, updateComponent, noop, {
    60. before () {
    61. if (vm._isMounted && !vm._isDestroyed) {
    62. callHook(vm, 'beforeUpdate')
    63. }
    64. }
    65. }, true /* isRenderWatcher */)
    66. hydrating = false
    67. // manually mounted instance, call mounted on self
    68. // mounted is called for render-created child components in its inserted hook
    69. // 判断为根节点
    70. if (vm.$vnode == null) {
    71. // 表示这个实例已经挂载了
    72. vm._isMounted = true
    73. // 执行mounted钩子函数
    74. callHook(vm, 'mounted')
    75. }
    76. return vm
    77. }

    vm.$vnode表示Vue实例的父虚拟Node,所以它为Null则表示当前时根Vue的实例