上一篇分析道_init() 方法就是Vue调用的第一个方法,也是Vue 构造函数最核心的方法,那么通过_init(),我们会得到一个怎么样的Vue实例?这一篇分析看看。
_init定义
先,我们找到这个_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)
}
}
这里大致_init() 方法做了哪些事:
(1)在 this 对象上定义了两个属性:_uid 和 _isVue;
(2)performance做性能监测
(3)合并option,定义 options._isComponent;
(4)initProxy(vm)做代理拦截;
(5)初始化如下函数:
initLifecycle(vm) //生命周期变量初始化
initEvents(vm) //事件监听初始化
initRender(vm) //初始化渲染
callHook(vm, 'beforeCreate') //回调钩子beforeCreate
initInjections(vm) //初始化注入
initState(vm) // prop/data/computed/method/watch状态初始化
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') //回调钩子created
合并options
在使用 Vue 开发项目的时候,从官方注解上看,我们基本不会使用到if (options && options._isComponent) 这个 Vue 内部分支,而是走 else 分支,也就是这段代码:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
这样 Vue 第一步所做的事情就来了:使用策略对象合并options。
可以发现,Vue使用 mergeOptions 来处理我们调用Vue时传入的参数选项(options),然后将返回值赋值给 this.$options (vm === this),传给 mergeOptions 方法三个参数,我们分别来看一看,首先是:resolveConstructorOptions(vm.constructor)。
export function resolveConstructorOptions (Ctor: Class) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
因为resolveConstructorOptions(vm.constructor),,这里的let options = Ctor.options相当于:let options = Vue.options。 那么Vue.options的结构是什么?
我们回忆上篇,core/index.js中的initGlobalAPI(Vue),initGlobalAPI 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 在经过 initGlobalAPI 之后,会变成什么样?
进入/ src/core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = (obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
Vue初始化options,先循环ASSET_TYPES的值末尾加s作为键,值为null,然后加了options._base,最后还通过import builtInComponents from '../components/index' 拷贝了keepalive组件,最后得到ASSET_TYPES为:
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
分析出,Vue通过initGlobalAPI 初始化后是这个样子!列出来!
Vue.config
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick
Vue.options = {
components: {
KeepAlive
},
directives: {},
filters: {},
_base: Vue
}
Vue.use
Vue.mixin
Vue.cid = 0
Vue.extend
Vue.component = function(){}
Vue.directive = function(){}
Vue.filter = function(){}
Vue.prototype.$isServer
Vue.version = '__VERSION__'
extend方法目前理解为将第二个值复制给第一个值(浅拷贝),(keepalive是一个内容很多的重要文件,在src/core/components中,标记以后用),最下面四个方法分别加了use,mixin,extend(和上边的不是一个),并在extend中使Vue.cid=0,最后一个方法被直接调用。
编译入口运行文件entry-runtime.js
下一个就是 entry-runtime.js 文件了,entry-runtime.js 文件主要做了三件事儿:
(1) 覆盖 Vue.config 的属性,将其设置为平台特有的一些方法
(2) Vue.options.directives 和 Vue.options.components 安装平台特有的指令和组件
(3) 在 Vue.prototype 上定义 patch 和 $mount
经过 entry-runtime.js 文件之后,Vue 又会变成下面这个样子:
// 安装平台特定的utils
Vue.config.isUnknownElement = isUnknownElement
Vue.config.isReservedTag = isReservedTag
Vue.config.getTagNamespace = getTagNamespace
Vue.config.mustUseProp = mustUseProp
// 安装平台特定的 指令 和 组件
Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue
}
Vue.prototype.__patch__
Vue.prototype.$mount
这里要注意的是 Vue.options 的变化。另外这里的 $mount 方法很简单:
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
首先根据是否是浏览器环境决定要不要 query(el) 获取元素,然后将 el 作为参数传递给mountComponent。
最后一个处理 Vue 的文件就是入口文件 entry-runtime-with-compiler.js 了,该文件做了两件事:
(1) 缓存来自 entry-runtime.js 文件的 $mount 函数
const mount = Vue.prototype.$mount
然后覆盖覆盖了 Vue.prototype.$mount
(2) 在 Vue 上挂载 compile
Vue.compile = compileToFunctions
compileToFunctions 函数的作用,就是将模板 template 编译为render函数。
至此,我们算是还原了 Vue 构造函数,总结一下:
(1) Vue.prototype 下的属性和方法的挂载主要是在 src/core/instance 目录中的代码处理的
(2) Vue 下的静态属性和方法的挂载主要是在 src/core/global-api 目录下的代码处理的
(3) entry-runtime.js 主要是添加web平台特有的配置、组件和指令,entry-runtime-with-compiler.js 给Vue的 $mount 方法添加 compiler 编译器,支持 template。