上次我们已经从总体上来看了new Vue()时,beforeCreate生命周期之前的一些操作,这次我们来看下beforeCreate和created之间都做了些啥
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
vue2.0源码-initMixin(二)
initInjections(vm)
我们先看源码然后一句句解析,通过名字其实我们可以猜测出来,这里就是处理inject的地方,不了解的可以去看下inject和provide,vue官网的解释为,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
先看第一句,resolveInject方法
const result = resolveInject(vm.$options.inject, vm)
我们可以先写入一个inject,然后通过断点的方式,查看这里resolveInject的值是什么样的
在下图中我们可以看出来,我们传入的inject数组['msg']变成了一个对象,这个转换操作是在之前_init方法最开始的地方,进行合并options调用mergeOptions时,在这个方法里面通过normalizeInject去转换的,这个我们后面再讲。
- 首先使用了 Object.create(null) 创建了一个以null为原型的空对象
- hasSymbol是用来判断开发者的当前环境是否支持Symbol和Reflect.ownKeys,如果支持则利用Reflect.ownKeys去获取一个以当前对象key为值得数组。反之则使用Object.keys去获取。
const result = Object.create(null)
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
接下来我们分析for循环里面的操作
先判断如果key===ob,则停止这次循环,执行下一次,在这里先要实现的效果就是跳过key为__ob__的值。
if (key === "__ob__") continue;
- 这一段代码的意思就是,通过利用while循环一直往组件的父级寻找与inject的key对应的provide的值,如果找到了,则终止循环,并将值赋给result,否则就继续往父级,父级的父级这样不断向上寻找,直到根组件。
- 并且初始化provided的操作initProvide在initInjections后面,按照父子组件的生命周期执行顺序父beforeCreate->父created->父beforeMount->子beforeCreate->子created,父组件的initProvide也已经初始完成了,但自身的并没有,所以自己的source._provided为undefined,不会去验证组件本身的provide。
const provideKey = inject[key].from;
let source = vm;
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break;
}
source = source.$parent;
}
最后一段的意思是,当循环到最后,source已经是root组件的父级也就是undefined并且没有找到与其对应的provide,但inject[key]存在默认值default时,则将default赋值给result[key],否则即没有对应的参数也没有默认值,则进行报错警告。
if (!source) {
if ("default" in inject[key]) {
const provideDefault = inject[key].default;
result[key] =
typeof provideDefault === "function"
? provideDefault.call(vm)
: provideDefault;
} else if (process.env.NODE_ENV !== "production") {
warn(`Injection "${key}" not found`, vm);
}
}
随后我们便可以回到initInjections方法上了,其主要代码就下面几句话,toggleObserving用于切换defineReactive注册的值是否具有响应性,也就是父组件中provide发送的值变化时,inject接受的值不会发生变化。除非inject接受的值本身就具备响应性。
toggleObserving(false);
defineReactive(vm, key, result[key])
toggleObserving(true);
initState(vm)
initState看名字,咱也能猜的出来,这个就是用来给一些属性进行赋值操作的,props,methods,data,computed,watch这些属性。
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps
用于赋值和初始化vm._props操作,由于源码太多就不一次性贴出来。下面代码主要操作了
- 定义了vm._props对象
- vm.$options._propKeys
- 定义了isRoot用于判断是否为根组件
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
- 然后我们看,下面的循环
- 当组件不是根组件时,关闭响应式。
- 判断是否为开发环境,如果为开发环境
- 先用hyphenate 方法对我们的key进行转换并且利用闭包的方式进行了缓存操作,这个方法的主要作用是将我们传入的key,如DemoD转换为demo-d。
- isReservedAttribute是用来判断,你使用的key是不是一些关键字,如key,ref,slot,slot-scope,is。如果是的话,就提出警告。
- config.isReservedAttr这个一直都是false,目前没有理解具体作用。
- 然后调用defineReactive方法,重写属性的set,get方法。
- 生产环境则直接调用defineReactive方法
- 最后判断我们的this对象上存不存在该属性,不存在时,则将属性赋值给this,并重写set,get方法与_props连接起来,方便我们使用this.直接使用。
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
initMethods
initMethods用于初始化,我们写在methods里面的方法。我们直接看源码,这个简单一点。
- 从options获取props,用于判断props和methods是否存在命名重复。
- 然后循环methods,验证methods[key]是否为一个方法,验证key命名规范,不能重复定义,不能以$或者_为开头命名。
- 最后将方法直接定义在this上,并修改方法里面的this指向为vm。
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props;
for (const key in methods) {
if (process.env.NODE_ENV !== "production") {
if (typeof methods[key] !== "function") {
warn(
`Method "${key}" has type "${typeof methods[
key
]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
);
}
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, vm);
}
if (key in vm && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
);
}
}
vm[key] =
typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
}
}
后面initData,initComputed,initWatch我们到时候单独分析其实现的原理。
initProvide(vm)
export function initProvide(vm: Component) {
const provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === "function" ? provide.call(vm) : provide;
}
}
initProvide就很简单了,只是一个简单的赋值操作,将我们写入的provide赋值给vm的_provided属性,并且如果传入的provide是个函数则改变他的this指向为vm,否则直接赋值。
关于initMixin的大概概述就到这里了,后面我们在深入去了解一下,合并$options都做了些什么操作,data的响应式,computed和watch的实现方式。