Vue 2.x源码学习:应用初始化大致流程
内容乃本人学习Vue2源码的一点笔记,若有错误还望指正。
源码版本:
vue: 2.6
vue-loader: 13.x
vue-template-compiler: 2.6
相关学习笔记:
- 数据响应式改造
- render方法、模板解析和依赖收集概述
我们使用vue-cli搭建vue 2.x项目时,大致由如下代码来做一个vue应用的初始化:
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
}).$mount("#app");
我们可以就从此处开始对Vue的认识。可以看到,这里表面上只做了一个简单的工作,就是通过new操作创建了一个vue的实例,并传递了一个配置项对象,该对象包含了一个render方法。
根据这个调用,我们找到
src/core/instance/index.js
文件,内容如下: // src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
内容也很直观,这里定义了一个只接受new构造调用的Vue Function,并对Vue进行了一系列的混入操作。
再粗浅地看一下这些Mixin都做了什么,可以看到是往Vue的prototype对象上挂了一些属性和方法。
大致如下:
Vue.prototype
|- initMixin
|- _init(options?: Object)
|- stateMixin
|- $data
|- $props
|- $set(target: Array<any> | Object, key: any, val: any): any <- ../observer/index
|- $delete(target: Array<any> | Object, key: any) <- ../observer/index
|- $watch(expOrFn: string | Function, cb: any, options?: Object): Function
|- eventMixin
|- $on(event: string | Array<string>, fn: Function): Component
|- $once(event: string, fn: Function): Component
|- $off(event?: string | Array<string>, fn?: Function): Component
|- $emit(event: string): Component
|- lifecycleMixin
|- $_update(vnode: VNode, hydrating?: boolean)
|- $forceUpdate()
|- $destrouy()
|- renderMixin
|- $nextTick(fn: Function)
|- _render(): VNode
Vue的函数体中,调用了一个
_init
的方法,并将参数传入,可以看到, _init
方法是在initMixin中定义的。 继续看
_init
方法的定义: // src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
见名知意,这个函数是对vue实例做一系列的初始化操作。
-
获取vue实例的构造器以及父级构造器(依次递归)上的配置项,以及参数传递进来的配置项,在加上实例自带的属性,都合并到一起,挂在实例的$option属性身上
-
将vue实例自身挂在_renderProxy属性上
-
初始化数据和方法前做一些准备工作
- initLifecycle:初始化生命周期
- initEvents:初始化事件
- initRender:初始化render
- 触发
钩子beforeCreate
-
初始化数据和方法
-
initInjections:处理$options.inject,对注入的数据做响应式处理
-
initState做的几件事
-
initProps:对$options.props做响应式处理
-
initMethods:对$options.methods对象做处理,将所有的方法直接挂在实例对象上,并将方法的this绑定到vue实例对象
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
-
initData:对$options.data进行observe
,继续追踪可以看到observe(data, true /* asRootData */)
方法是对data进行响应式处理,返回一个observe
实例Observer
// src/core/boserver/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
} -
initComputed:处理计算属性$options.computed
给每个计算属性创建Watcher实例
// src/core/instance/state.js
const computedWatcherOptions = { lazy: true }
function initComputed(vm: Component, computed: Object) {
// ...
const watchers = (vm._computedWatchers = Object.create(null))
// ...
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = isFunction(userDef) ? userDef : userDef.get
if (__DEV__ && getter == null) {
warn(`Getter is missing for computed property "${key}".`, vm)
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
// ...
}
// ...
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
// ...
}
// ...
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
可以看到创建Watcher实例时传入一个配置项
,再看{ lazy: true }
的构造器中的代码,即默认Watcher
为watcher.dirty
,所以执行true
,watcher.evaluate()
。watcher.get()
会去执行计算方法或者计算属性的watcher.get()
方法,即get()
。this.getter.call(vm, vm)
// src/core/observer/watcher.js
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
// ...
this.lazy = !!options.lazy
// ...
} else {
// ...
}
// ...
this.dirty = this.lazy // for lazy watchers
// ...
}
evaluate () {
this.value = this.get()
this.dirty = false
}
get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e: any) {
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
}
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
} -
initWatch:处理自定义监听$options.watch
执行了
方法,可以先看下它的定义:$watch
// src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | (() => any),
cb: any,
options?: Record<string, any>
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn() {
watcher.teardown()
}
}
可以看到也是创建了一个
实例对象。Watcher
-
-
initProvide:处理$options.provide,将provide的数据(或者provide执行后的数据)挂在实例的
属性上_provide
-
触发
钩子created
-
-
最后执行
方法,执行挂载流程,由于挂载的方式由平台决定,所以vm.$mount
的方法并未定义在$mount
中;web端的src/core
方法定义在$mount
中。src/platforms/web/runtime/index.js
// 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(this, el, hydrating)
中。src/core/instance/lifecycle.js
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (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._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._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
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
}
见名知意,是对挂载的处理:
-
拿到
放在vm.$el上el
-
确认是否有
,没有则赋值创建一个空的VNode实例的方法vm.$options.render
-
触发
钩子beforeMount
-
创建一个新的
实例,用于实例更新后触发重新渲染Watcher
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
并传递一个before方法,用于在组件更新前触发
钩子beforeUpdate
-
触发
钩子mounted
-
Vue应用初始化大致就是这样一个流程