系列文章
- [Vue源码学习] new Vue()
- [Vue源码学习] 配置合并
- [Vue源码学习] $mount挂载
- [Vue源码学习] _render(上)
- [Vue源码学习] _render(下)
- [Vue源码学习] _update(上)
- [Vue源码学习] _update(中)
- [Vue源码学习] _update(下)
- [Vue源码学习] 响应式原理(上)
- [Vue源码学习] 响应式原理(中)
- [Vue源码学习] 响应式原理(下)
- [Vue源码学习] props
- [Vue源码学习] computed
- [Vue源码学习] watch
- [Vue源码学习] 插槽(上)
- [Vue源码学习] 插槽(下)
前言
在前面的章节中,我们已经创建了一个Vue的实例,那么接下来,就可以通过$mount方法,将该实例挂载到页面中。
那么接下来,我们就来看看对于运行时版本来说,$mount是如何进行挂载的。
$mount
对于Web平台来说,$mount方法是在引入Vue时添加到Vue.prototype上的,代码如下所示:
/* 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)
}
可以看到,在$mount方法中,首先通过query方法找到el选项对应的DOM元素,然后调用mountComponent方法:
/* core/instance/lifecycle.js */
export function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// ...
callHook(vm, 'beforeMount')
let updateComponent
// 依赖收集 + 派发更新
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 渲染Watcher
// 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() {
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
}
可以看到,mountComponent方法的逻辑很简单,主要就是创建一个渲染Watcher,对于每个Vue实例来说,有且仅有一个渲染Watcher与之对应,通过此渲染Watcher,Vue就可以完成依赖收集和派发更新的整个过程,而这里的关键方法就是updateComponent,它里面的_render方法就是用来创建VNode并收集依赖,_update方法就是将VNode渲染成真实的DOM,里面的具体细节之后会详细介绍,在mountComponent方法的最后,对于根组件来说,它会调用mounted钩子函数,通知应用已经完成挂载。
那么接下来,我们就来看看在在创建渲染Watcher的过程中,Vue内部又做了哪些工作。
渲染Watcher
Watcher的代码如下所示:
/* core/observer/watcher.js */
export default class Watcher {
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// 渲染Watcher
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// ...
}
// 在创建渲染Watcher时,会直接调用get方法
this.value = this.lazy
? undefined
: this.get()
}
get() {
// 将Dep.target设置为当前正在处理的Watcher实例
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
// 清理多余的依赖
this.cleanupDeps()
}
return value
}
}
可以看到,在创建渲染Watcher的过程中,首先会将刚刚的updateComponent方法赋值给Watcher实例的getter,然后直接调用watcher.get方法,在此方法中,会先后调用两个关键的方法pushTarget和popTarget,我们先来看看这两个方法的实现:
/* core/observer/dep.js */
Dep.target = null
const targetStack = []
export function pushTarget(target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
可以看到,这里的targetStack就是模拟了一个栈结构,用来存储各种watcher的实例,调用pushTarget会先将当前watcher入栈,然后将其赋值给Dep.target,调用popTarget会先将最顶层的watcher出栈,然后将Dep.target指向下一个watcher,所以在这两个方法中间,就可以在任意位置通过Dep.target找到当前正在处理的watcher实例。
回到上面的get方法中,首先会调用pushTarget,将Dep.target指向当前的渲染watcher,然后就会调用watcher.getter方法,也就是上面传入的updateComponent方法,在该方法中,Vue首先会调用_render方法生成VNode,然后调用_update方法根据VNode渲染成真实的DOM,在调用完updateComponent方法后,就会调用popTarget方法,恢复Dep.target的指向,最后调用cleanupDeps方法,对多余的依赖进行清理。
此时,初始化渲染Watcher的工作也已经完成了,并且在_render的过程中,渲染Watcher也已经收集到了所有它需要依赖的数据,所以当这些数据发生变化时,就会通知渲染Watcher做更新操作,这部分内容会在之后的章节中详细介绍。
初始化的整体流程
从这几章节中,我们知道了根组件的初始化和挂载逻辑,而对于子组件来说也是类似的,它也会在父组件执行挂载的过程中进行初始化和挂载,具体的执行流程如下图所示:
总结
在Vue中,组件的初始化和挂载是两个独立的模块,在调用$mount进行挂载的过程中,Vue会创建一个渲染Watcher,然后立即调用一次watcher.get,进而完成依赖收集和首次挂载的逻辑,当Watcher所依赖的数据发生变化时,又会通知此Watcher做更新操作,从而重新收集依赖和派发更新。