Vue 根组件的挂载有两种方式,一种是在根组件的 options 中配置 el,第二种是在根组件实例化后调用 $mount,两者是冲突的,有了前者,后者便不会生效。当然前者在初始化后调用的也还是 $mount。
let me see see $mount
仅讨论 web 情况
// src\core\instance\init.js =======================================================================================if (vm.$options.el) {vm.$mount(vm.$options.el)}// src\platforms\web\runtime\index.js ==============================================================================Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean): Component {// 入参及环境的校验,获取到 el 对应的 DOM,否则则是 undefinedel = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating)}// src\platforms\web\entry-runtime-with-compiler.js ================================================================// 缓存原先的 $mount 函数,也就是上面运行时的 $mount,在最后还是会调用的const mount = Vue.prototype.$mount// 核心目的是得到一个有效的 render 函数Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean): Component {// 获取对应 DOMel = el && query(el)// 如果 DOM 是 body 或者 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}// 获取初始化之后的 optionsconst options = this.$options// resolve template/el and convert to render function// 如果未编写 render 函数,如果没有,则用各种内容尝试得到一个有效的 template 来使用if (!options.render) {let template = options.template// 如果有 templateif (template) {if (typeof template === 'string') {// 如果 template 是 string,且不是已 '#' 号开头的,则直接将其当做 template// 如果 template 是 string 且已 '#' 号为开头,则视为 css 选择器,然后找到对应 DOM 的 innerHTML 为 template// 如果对应的 innerHTML 为空则抛出非法报错if (template.charAt(0) === '#') {template = idToTemplate(template)if (process.env.NODE_ENV !== 'production' && !template) {warn(`Template element not found or is empty: ${options.template}`,this)}}} else if (template.nodeType) {// 如果 template 直接是个 DOM 了,则直接取其 innerHTMLtemplate = template.innerHTML} else {// 如果以上条件均不成立,就是既没有 render, template 也不是 string 或者 DOM,说明 template 是个非法的内容,则直接报错结束if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)}return this}} else if (el) {// 如果既没有 render 也没有 template,则将挂载的 DOM 的 outerHTML 为 templatetemplate = getOuterHTML(el)}// 如果存在 template,一般经过上面的过滤应该存在了if (template) {// 性能计算,passif (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile')}// 将 template 转为 render 函数赋值 options,返回const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns// 性能计算,passif (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile end')measure(`vue ${this._name} compile`, 'compile', 'compile end')}}}// 调用原生的 $mountreturn mount.call(this, el, hydrating)}
mountComponent
// src\core\instance\lifecycle.jsexport function mountComponent (vm: Component,el: ?Element,hydrating?: boolean): Component {// this.$el 存储的是挂载的 DOMvm.$el = el// 如果没有 render 函数,// 没有 render 函数有两种情况// 一是,仅用的 runtime 包,却没有写 render// 二是,用了 runtime-with-compiler 包,但是没有有效的 el, template, render 的情况,才会没有 render// 下面均是因为上面的两种都属于非法行为而抛出的错误if (!vm.$options.render) {// 先初始化一个空的 VNodevm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */// 如果写了纯 template 或者有 el,则告诉开发者,当前的开发环境是没有 compile 包的, 用 template 是无效的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 {// 如果没有,render,el,template,则抛出错误,提示需要有效的配置warn('Failed to mount component: template or render function not defined.',vm)}}}// 触发 beforeMount 生命周期函数callHook(vm, 'beforeMount')// 开始挂载// updateComponent 把渲染函数生成的虚拟DOM渲染成真正的DOMlet updateComponent// if-else 实际没差,前者只是拆分了后者运行,并加了一些性能分析if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst startTag = `vue-perf-start:${id}`const endTag = `vue-perf-end:${id}`mark(startTag)const vnode = vm._render()mark(endTag)measure(`vue ${name} render`, startTag, endTag)mark(startTag)vm._update(vnode, hydrating)mark(endTag)measure(`vue ${name} patch`, startTag, endTag)}} else {updateComponent = () => {// 1. vm._render() 将 render 变成虚拟 DOM// 2. vm._update() 将 虚拟 DOM 变成真实 DOMvm._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, {// before 用于每次数据发生变化时的前置调用,正好与 beforeUpdate 的意图吻合,当然前提是实例已经被挂载,同时未被销毁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 hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm}
