-
Notifications
You must be signed in to change notification settings - Fork 0
Description
通过上篇文章 Vue 源码学习(二)- 构造函数,我们知道了 Vue 的构造被多个函数包装处理,所以我们今天来分析下第一个包装函数 initMixin,它到底做了哪些处理呢?
初始化
我们可以看到,在 initMixin 方法中,在 Vue 的原型链上增加了 _init 方法,这个方法是不是很眼熟,在分析构造函数的时候,它就调用了这个方法,原来是在这里定义的。
// src/core/instance/init.js
let uid = 0
export function initMixin (Vue: Class<Component>) {
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)
}
}
}首先声明了常量 vm,指向当前 Vue 实例,然后添加了一个唯一标示 _uid,其值为 uid。uid 初始值为 0,所以每次实例化一个 Vue 实例之后,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)
}
// 中间代码...
/* 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)
}我们这边不做详细介绍,主要还是来看下中间部分的内容:
// 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')我们来逐行分析下,首先是在 Vue 实例上添加 _isVue 属性,并设置其值为 true。看注释知道是为了避免被响应系统观察,也就是说,如果一个对象拥有 _isVue 属性并且值为 true,那么就代表该对象是 Vue 实例,这样就可以避免被观察。
接下来是一个 options && options._isComponent 判断,它是用来初始化内部组件用到的,这块内容等讲到组件部分再来分析,所以先看 else 部分。
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)在 Vue 实例上添加了 $options 属性,在官方文档中,我们也能看到它是用于当前 Vue 实例的初始化选项,它是通过 mergeOptions 函数创建而来。该函数有三个参数,第一个是通过函数 resolveConstructorOptions 返回得到,第二个是调用 Vue 构造函数时透传进来的对象,第三个参数是当前的 Vue 实例。
在讲 mergeOptions 前,我们先来看下 resolveConstructorOptions 函数,看名字应该是用来解析构造函数的 options,传的参数也是实例的构造函数。看下具体实现:
export function resolveConstructorOptions (Ctor: Class<Component>) {
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
}通过上面调用时传递的参数 vm.constructor 知道,这里的 Ctor 是当前实例的构造函数,也就是 Vue 本身。那么这里一定是 Vue 吗?通过下面 if 判断中的 super 知道,Ctor 也有可能是个子类,因为只有之类才有 super 属性。也就是说 Ctor 有可能是通过 Vue.extend 创造的一个子类。
有了上面的理解,下面的 if 判断也比较好理解了。如果当前是 Vue 的实例,那么直接返回 Vue.options 的值。根据前面文章构造函数篇章知道,Vue.options 的值是通过全局 global-api 来初始化的,内容如下:
Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: Object.create(null),
_base: Vue
}那么如果当前是子类的实例呢?
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions通过递归调用 resolveConstructorOptions 来获取父类的 options。
而 Ctor.superOptions 的值我们前面的文章 Vue.extend 中提到
Sub.superOptions = Super.options也就是说它是通过 Vue.extend 继承而来的父类的静态属性 options 值。
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
}
}当 superOptions 和 cachedSuperOptions 不相等时,更新 Ctor.superOptions。这种情况比如如下例子:
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
});
Vue.mixin({ data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}});
new Profile().$mount('#mount-point');因为在执行 Vue.mixin 时,修改了 Vue.options 的值:
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
}如果没有这个 if 判断,页面将只显示 aka,因为 Vue.options 上的值还是旧的。
接下来通过 resolveModifiedOptions 方法来检测“自身”的 options 是否发生变化,如果有变化,返回修改的值。Ctor.extendOptions 作为传递给 Vue.extend 的参数,和 superOptions 合并并赋值给 options,最终返回 options。
小结
上面说了这么多,似乎都是和 options 有关。而且后面的内容,都会使用到这个 options。既然 options 这么重要,我们下一篇来说说 options 的合并策略 mergeOptions 函数是怎么实现的。