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 文件中定义
const mount = Vue.prototype.$mount // 缓存原型上的$mount方法// 重新定义$mount方法Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean): Component {el = el && query(el)/* istanbul ignore if */// Vue不能挂载在body、html这样的根节点上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// resolve template/el and convert to render function// 没有定义render方法if (!options.render) {let template = options.templateif (template) {if (typeof template === 'string') {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) {template = template.innerHTML} else {if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)}return this}} else if (el) {template = getOuterHTML(el)}if (template) {/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile')}// compileToFunctions方法 写了el或者template属性最终都会转换成render方法 "在线编译"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/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile end')measure(`vue ${this._name} compile`, 'compile', 'compile end')}}}// 调用原先原型上的$mount方法挂载return mount.call(this, el, hydrating)}
原先原型上的 $mount 方法
在 src/platform/web/runtime/index.js 中定义,之所以这么设计完全是为了复用,因为它是可以被runtime only版本的Vue直接使用的
import { mountComponent } from 'core/instance/lifecycle'// public mount method// 公用的挂载方法Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating)}
参数
- el - 挂载的元素,可以是字符串,也可以是DOM对象,如果是字符串在浏览器环境下会调用query方法转换成DOM对象的
hydrating - 和服务端渲染相关,在浏览器环境下不需要传第二个参数
mountComponent方法
定义在 src/core/instance/lifecycle.js 文件中
export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean): Component {vm.$el = elif (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (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/* istanbul ignore if */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)// 调用vm.render方法先生成虚拟Nodeconst vnode = vm._render()mark(endTag)measure(`vue ${name} render`, startTag, endTag)mark(startTag)// 调用vm.update更新DOMvm._update(vnode, hydrating)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// 核心:实例化一个渲染Watcher 回调函数中会调用updateComponent方法// Watcher作用// 一是初始化的时候会执行回调函数// 二是当vm实例中的监测的数据发生变化的时候执行回调函数new Watcher(vm, updateComponent, noop, {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// 执行mounted钩子函数callHook(vm, 'mounted')}return vm}
vm.$vnode表示Vue实例的父虚拟Node,所以它为Null则表示当前时根Vue的实例
