Vue实例挂载($mount)
承接上一节数据驱动流程分析1
在实例化Vue是执行了this._init()中最后执行的if (vm.$options.el) { vm.$mount(vm.$options.el) },也就是$mount方法。
vm.mount会最终调用src/platforms/web/entry-runtime-with-compiler.js中的mount
// src/platforms/web/entry-runtime-with-compiler.js
// 先将原型链上的$mount方法进行缓存,然后重新定义了$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* query(el) 方法 将el转化成DOM对象
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
}
*/
/* istanbul ignore if */
// 判断了el不能为 html元素 和 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
}
// 拿到了options配置
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
// 这里我们没有template参数
if (template) {
// ....
} else if (el) {
template = getOuterHTML(el)
// getOuterHTML 将DOM结构转化成 String 具体可见 element.outerHTML api
}
if (template) {
// ...
// 拿到编译相关的配置,就是这里将template转化成了render函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 将render函数加入到options中去
options.render = render
// ...
}
}
// 最后执行一开始保存下来的mount方法
return mount.call(this, el, hydrating)
}
这一块的代码主要做了两件事情: 1.拿到template 2.将template转化成render 然后执行了mount()方法
// src/platforms/web/runtime/index
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// el 已经是DOM结构了
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
最终调用了mountComponent()方法
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 此处判断如果没有render函数的话,会抛出警告,也恰恰说明了Vue实际接受的是一个render函数
// template在complier版本下会被转化成render函数
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
//...
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
)
//...
}
}
// 出发了beforeMount钩子
callHook(vm, 'beforeMount')
let updateComponent
// 性能埋点相关
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
// ...
} else {
// 最后执行了updateComponent方法
/* updateComponent 被赋值成了一个函数,vm._update(vm._render(), hydrating) */
updateComponent = () => {
// vm._update() 是将实例生成为Virtual Dom
// vm._render() 将Virtual Dom 渲染为真是Dom
vm._update(vm._render(), hydrating)
}
}
// 将updateComponent作为new Watcher的参数传入
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true )
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 是Vue中非常核心的概念,渲染Watcher , computed Wathcer 还有user Watch 都是通过 Watcher这个class实现(观察者模式)。
class Watcher {
vm: Component;
lazy: boolean;
getter: Function;
value: any;
// ...
constructor (
vm: Component,
expOrFn: string | Function, // expOrFn = updateComponent()
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// ...
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy // 为false
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
// ...
// parse expression for getter
if (typeof expOrFn === 'function') {
// 赋值给this.getter
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
// ...
}
// 执行 this.get()方法 并 赋值给value
this.value = this.lazy ? undefined : this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
// 执行了this.getter 方法
// value = vm._update(vm._render(), hydrating)
value = this.getter.call(vm, vm)
}
// ...
}
将template转化成render函数,render函数将DOM转化成了 虚拟dom 然后通过vm._update渲染为真实DOM