解析:
runtime-with-compiler版本入口: src/platforms/web/entry-runtime-with-compiler.js
在这个文件中可以看到在Vue的原型上重新定义了$mount
方法。在重新定义之前,可以看到Vue的原型上是已经存在了$mount
这个方法的,重新定义的目的就是区别于runtime-only版本,在这之前需要compiler。
/* src/platforms/web/entry-runtime-with-compiler.js */
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
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
if (!options.render) {
let template = options.template
if (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')
}
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.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')
}
}
}
return mount.call(this, el, hydrating)
}
el = el && query(el)
这行是根据传入的el去document中查找元素,本质就是调用了document.querySelector
。el元素也是有限制的,它不能是body和html元素,因为在进行挂载的时候,会整个的进行替换。
/* src/platforms/web/util/index.js */
export function query (el: string | Element): Element {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
return document.createElement('div')
}
return selected
} else {
return el
}
}
接下来就是去看options
中有没有定义render
函数,如果不存在则需要根据template
去编译从而生成一个render
函数。实质上Vue最后的渲染就只依赖于render
函数。中间的一大段过程就是在判断template的合法性然后将template编译成render函数。这个部分就是比runtime-only版本多出来的complier部分。最后调用mount.call(this, el, hydrating),该mount方法就是runtime-only版本中使用的Vue.prototype.$mount
方法。
回到runtime-only版本的Vue.prototype.$mount
方法
/* src/platforms/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)
}
最后调用了mountComponent
这个方法。这个方法定义在src/core/instance/lifecycle.js中
mountComponent
方法一开始就会判断render函数是否存在,在非生产环境的时候就会进行报错提示。这个方法中定义了一个Watcher对象,是一个renderWatcher
。
/* src/core/instance/lifecycle.js */
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
查看watcher
的构造函数,updateComponent
是作为expOrFn(string | Function)
参数传递进入的,当该参数是一个函数的时候,会把该函数赋值给watcher
的getter
属性。当数据变更的时候,触发视图更新。
updateComponent
实质上先调用了vm._render()
来获取vnode
,然后调用vm._update()
来将虚拟dom和真实dom进行挂钩。