Vue 根组件的挂载有两种方式,一种是在根组件的 options 中配置 el,第二种是在根组件实例化后调用 $mount,两者是冲突的,有了前者,后者便不会生效。当然前者在初始化后调用的也还是 $mount。

let me see see $mount

仅讨论 web 情况

  1. // src\core\instance\init.js =======================================================================================
  2. if (vm.$options.el) {
  3. vm.$mount(vm.$options.el)
  4. }
  5. // src\platforms\web\runtime\index.js ==============================================================================
  6. Vue.prototype.$mount = function (
  7. el?: string | Element,
  8. hydrating?: boolean
  9. ): Component {
  10. // 入参及环境的校验,获取到 el 对应的 DOM,否则则是 undefined
  11. el = el && inBrowser ? query(el) : undefined
  12. return mountComponent(this, el, hydrating)
  13. }
  14. // src\platforms\web\entry-runtime-with-compiler.js ================================================================
  15. // 缓存原先的 $mount 函数,也就是上面运行时的 $mount,在最后还是会调用的
  16. const mount = Vue.prototype.$mount
  17. // 核心目的是得到一个有效的 render 函数
  18. Vue.prototype.$mount = function (
  19. el?: string | Element,
  20. hydrating?: boolean
  21. ): Component {
  22. // 获取对应 DOM
  23. el = el && query(el)
  24. // 如果 DOM 是 body 或者 body 则非法,抛出错误
  25. if (el === document.body || el === document.documentElement) {
  26. process.env.NODE_ENV !== 'production' && warn(
  27. `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
  28. )
  29. return this
  30. }
  31. // 获取初始化之后的 options
  32. const options = this.$options
  33. // resolve template/el and convert to render function
  34. // 如果未编写 render 函数,如果没有,则用各种内容尝试得到一个有效的 template 来使用
  35. if (!options.render) {
  36. let template = options.template
  37. // 如果有 template
  38. if (template) {
  39. if (typeof template === 'string') {
  40. // 如果 template 是 string,且不是已 '#' 号开头的,则直接将其当做 template
  41. // 如果 template 是 string 且已 '#' 号为开头,则视为 css 选择器,然后找到对应 DOM 的 innerHTML 为 template
  42. // 如果对应的 innerHTML 为空则抛出非法报错
  43. if (template.charAt(0) === '#') {
  44. template = idToTemplate(template)
  45. if (process.env.NODE_ENV !== 'production' && !template) {
  46. warn(
  47. `Template element not found or is empty: ${options.template}`,
  48. this
  49. )
  50. }
  51. }
  52. } else if (template.nodeType) {
  53. // 如果 template 直接是个 DOM 了,则直接取其 innerHTML
  54. template = template.innerHTML
  55. } else {
  56. // 如果以上条件均不成立,就是既没有 render, template 也不是 string 或者 DOM,说明 template 是个非法的内容,则直接报错结束
  57. if (process.env.NODE_ENV !== 'production') {
  58. warn('invalid template option:' + template, this)
  59. }
  60. return this
  61. }
  62. } else if (el) {
  63. // 如果既没有 render 也没有 template,则将挂载的 DOM 的 outerHTML 为 template
  64. template = getOuterHTML(el)
  65. }
  66. // 如果存在 template,一般经过上面的过滤应该存在了
  67. if (template) {
  68. // 性能计算,pass
  69. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  70. mark('compile')
  71. }
  72. // 将 template 转为 render 函数赋值 options,返回
  73. const { render, staticRenderFns } = compileToFunctions(template, {
  74. outputSourceRange: process.env.NODE_ENV !== 'production',
  75. shouldDecodeNewlines,
  76. shouldDecodeNewlinesForHref,
  77. delimiters: options.delimiters,
  78. comments: options.comments
  79. }, this)
  80. options.render = render
  81. options.staticRenderFns = staticRenderFns
  82. // 性能计算,pass
  83. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  84. mark('compile end')
  85. measure(`vue ${this._name} compile`, 'compile', 'compile end')
  86. }
  87. }
  88. }
  89. // 调用原生的 $mount
  90. return mount.call(this, el, hydrating)
  91. }

mountComponent

  1. // src\core\instance\lifecycle.js
  2. export function mountComponent (
  3. vm: Component,
  4. el: ?Element,
  5. hydrating?: boolean
  6. ): Component {
  7. // this.$el 存储的是挂载的 DOM
  8. vm.$el = el
  9. // 如果没有 render 函数,
  10. // 没有 render 函数有两种情况
  11. // 一是,仅用的 runtime 包,却没有写 render
  12. // 二是,用了 runtime-with-compiler 包,但是没有有效的 el, template, render 的情况,才会没有 render
  13. // 下面均是因为上面的两种都属于非法行为而抛出的错误
  14. if (!vm.$options.render) {
  15. // 先初始化一个空的 VNode
  16. vm.$options.render = createEmptyVNode
  17. if (process.env.NODE_ENV !== 'production') {
  18. /* istanbul ignore if */
  19. // 如果写了纯 template 或者有 el,则告诉开发者,当前的开发环境是没有 compile 包的, 用 template 是无效的
  20. if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
  21. vm.$options.el || el) {
  22. warn(
  23. 'You are using the runtime-only build of Vue where the template ' +
  24. 'compiler is not available. Either pre-compile the templates into ' +
  25. 'render functions, or use the compiler-included build.',
  26. vm
  27. )
  28. } else {
  29. // 如果没有,render,el,template,则抛出错误,提示需要有效的配置
  30. warn(
  31. 'Failed to mount component: template or render function not defined.',
  32. vm
  33. )
  34. }
  35. }
  36. }
  37. // 触发 beforeMount 生命周期函数
  38. callHook(vm, 'beforeMount')
  39. // 开始挂载
  40. // updateComponent 把渲染函数生成的虚拟DOM渲染成真正的DOM
  41. let updateComponent
  42. // if-else 实际没差,前者只是拆分了后者运行,并加了一些性能分析
  43. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  44. updateComponent = () => {
  45. const name = vm._name
  46. const id = vm._uid
  47. const startTag = `vue-perf-start:${id}`
  48. const endTag = `vue-perf-end:${id}`
  49. mark(startTag)
  50. const vnode = vm._render()
  51. mark(endTag)
  52. measure(`vue ${name} render`, startTag, endTag)
  53. mark(startTag)
  54. vm._update(vnode, hydrating)
  55. mark(endTag)
  56. measure(`vue ${name} patch`, startTag, endTag)
  57. }
  58. } else {
  59. updateComponent = () => {
  60. // 1. vm._render() 将 render 变成虚拟 DOM
  61. // 2. vm._update() 将 虚拟 DOM 变成真实 DOM
  62. vm._update(vm._render(), hydrating)
  63. }
  64. }
  65. // we set this to vm._watcher inside the watcher's constructor
  66. // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  67. // component's mounted hook), which relies on vm._watcher being already defined
  68. // 添加实例的观察者
  69. new Watcher(vm, updateComponent, noop, {
  70. // before 用于每次数据发生变化时的前置调用,正好与 beforeUpdate 的意图吻合,当然前提是实例已经被挂载,同时未被销毁
  71. before () {
  72. if (vm._isMounted && !vm._isDestroyed) {
  73. callHook(vm, 'beforeUpdate')
  74. }
  75. }
  76. }, true /* isRenderWatcher */)
  77. hydrating = false
  78. // manually mounted instance, call mounted on self
  79. // mounted is called for render-created child components in its inserted hook
  80. if (vm.$vnode == null) {
  81. vm._isMounted = true
  82. callHook(vm, 'mounted')
  83. }
  84. return vm
  85. }

compileToFunctions