Vue 2.x源码学习:应用初始化大致流程
内容乃本人学习Vue2源码的一点笔记,若有错误还望指正。
源码版本:
vue: 2.6
vue-loader: 13.x
vue-template-compiler: 2.6
相关学习笔记:
我们使用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 */)
,继续追踪可以看到observe
方法是对data进行响应式处理,返回一个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
的方法并未定义在src/core
中;web端的$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 }
见名知意,是对挂载的处理:
-
拿到
el
放在vm.$el上 -
确认是否有
vm.$options.render
,没有则赋值创建一个空的VNode实例的方法 -
触发
beforeMount
钩子 -
创建一个新的
Watcher
实例,用于实例更新后触发重新渲染updateComponent = () => { vm._update(vm._render(), hydrating) }
并传递一个before方法,用于在组件更新前触发
beforeUpdate
钩子 -
触发
mounted
钩子
-
Vue应用初始化大致就是这样一个流程
本文来自博客园,作者:beckyye,转载请注明原文链接:https://www.cnblogs.com/beckyyyy/p/16858949.html