从runtime with compiler版本的入口开始
src/platforms/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
) {
...
return mount.call(this, el, hydrating)
}
我们先来看这部分代码,首先将Vue
原型上的$mount
方法缓存到了一个变量mount
上,接着重新定义原型上的$mount
方法,最后返回原先方法的调用结果:return mount.call(this, el, hydrating)
这么做是为了复用$mount
方法,因为我们在runtime only版本中是没有用到这部分代码的,接下来我们看看这部分代码做了些什么。
el = el && query(el)
这里先是把传入的el
转换成DOM对象,el
即vm.$options.el
,它可能是一个字符串,或者是一个DOM对象,那我们具体看看query
方法的实现。
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
}
}
这个方法很简单,如果el
是字符串,则使用js api获取DOM节点,如果找不到节点则创建一个空的div
节点返回,如果el
本身就是DOM节点,则直接返回。
我们再回到$mount
的代码中:
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')
}
}
}
我们来分析下这部分代码,首先判断options
中是否存在render
函数,如果没有则执行这部分代码,主要是把template
编译成render
函数,以便后续渲染。
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
...
}
}
}
首先是判断有没有传template
参数,存在的话判断template
的类型,如果是以#
开头的值则调用idToTemplate(template)
返回结果赋值给template
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
idToTemplate
方法其实就是调用了前面说的query
方法,通过ID找DOM节点,然后返回其innerHTML
如果没有传template
,则去看el
,通过getOuterHTML(el)
返回innerHTML
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
getOuterHTML
返回el
节点的outerHTML
,如果没有outerHTML
的话则在el外面包一个div再返回它的outerHTML
template
最后就是一个HTML字符串,然后将其编译成render
函数:
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
最终这里会call
原先的$mount
方法,也就是在开头缓存的$mount
方法,我们看看接下来$mount
做了些什么。
再来到 runtime 的入口
上面讲到,runtime with compiler 版本入口把$mount
重新定义,然后再执行原先的$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)
}
核心代码就是return mountComponent(this, el, hydrating)
mountComponent 解析
src/core/instance/lifecycle
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
首先是判断是否定义了render,如果没有的话就使用createEmptyVNode
来创建一个空的Vnode
,关于Vnode
后续会详细分析,这里暂时不展开讲。
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
然后定义了updateComponent
函数,通过调用实例上的_update
方法来更新渲染,传入的两个参数,第一个就是前面一系列操作的render
函数,第二个参数与服务器渲染相关,这里不讲。
而这个updateComponent
函数在什么时候调用呢?
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
callHook(vm, 'mounted')
}
return vm
}
这里实例化了一个渲染Watcher,关于Watcher
,后续也会展开讲,这里大概了解一下它是通过观察者模式调用了updateComponent
函数,从而完成实例的挂载。