initInjections
在日常开发中用的少
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
从函数名字上来看,该函数是用来初始化实例中的inject选项的。说到inject选项,那必然离不开provide选项,这两个选项都是成对出现的
它们的作用是:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效
// 父级组件提供 'foo'
var Parent = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
函数分析
既然inject选项和provide选项都是成对出现的,那为什么在初始化的时候不一起初始化呢?为什么在init函数中调用initInjections函数和initProvide函数之间穿插一个initState函数呢?
这里所说的数据就是我们通常所写data、props、watch、computed及method,所以inject选项接收到注入的值有可能被以上这些数据所使用到,所以在初始化完inject后需要先初始化这些数据,然后才能再初始化provide
export function initInjections (vm: Component) {
// 把数组里面的key转化为对象
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 告诉defineReactive函数,仅仅是把键值添加到实例上,不需要转为响应式
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)
}
}
initState
这个很重要,用来初始化,实例的状态选项
export function initState (vm: Component) {
/*
用来存储当前实例的
Vue不再对所有数据都进行侦测,
而是将侦测粒度提高到了组件层面,
对每个组件进行侦测,所以在每个组件上新增了vm._watchers属性
,用来存放这个组件内用到的所有状态的依赖,当其中一个状态发生变化时,
就会通知到组件,
然后由组件内部使用虚拟DOM进行数据比对,
从而降低内存开销,提高性能。
*/
vm._watchers = []
/*
下面这几个初始化,是有顺序的
所有在data 里面可以使用props
*/
const opts = vm.$options
// 先判断实例中是否有props选项,如果有,就调用props选项初始化函数initProps去初始化props选项
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
/*
如果有data就初始化data,
没有的话,就把data当做空对象,并将其转化成响应式
*/
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)
}
}
normalizeProps
normalizeProps 函数,把各种props写法,进行规范
initProps
initProps函数的定义位于源码的src/core/instance/state.js中
/*
初始化 props
这个函数,主要是把传过来的props的key,value 放到vm实例上,
这样在组件里,可以直接this.访问到
*/
function initProps (vm: Component, propsOptions: Object) {
/* 父组件传入的真实props对象 */
const propsData = vm.$options.propsData || {}
// 指向vm._props的指针,所有设置到props变量中的属性都会保存到vm._props中
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
/*
指向vm.$options._propKeys的指针,缓存props对象中的key,
将来更新props时只需遍历vm.$options._propKeys数组即可得到所有props的key
*/
const keys = vm.$options._propKeys = []
/* 当前组件是否是根组件 */
const isRoot = !vm.$parent
// root instance props should be converted
/*
如果不是根组件,就不需要转化为响应式
*/
if (!isRoot) {
toggleObserving(false)
}
/*
遍历 props
*/
for (const key in propsOptions) {
/* _propKeys存入key */
keys.push(key)
/* 校验数据类型是否匹配,并且,取出父组件传过来的值 */
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
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
)
}
/*
把键值放到vm._props中
*/
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 {
/*
把键值放到vm._props中
*/
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.
/*
判断这个key在当前实例中是否存在
如果不存在,则调用proxy函数在vm上设置一个以key为属性的代码,
当使用vm[key]访问数据时,其实访问的是vm._props[key]
*/
if (!(key in vm)) {
/* 代理this.key === this._props.key */
proxy(vm, `_props`, key)
}
}
/* 再把defineReactive开关打开 */
toggleObserving(true)
}
validateProp
/*
校验props的类型是否正确
并且返回value的值
key: props的key
propOptions: 子组件的props对象
propsData: 父组件的数据对象
vm: Vue实例
*/
export function validateProp (
key: string,
propOptions: Object,
propsData: Object,
vm?: Component
): any {
/* 当前key在propOptions中对应的值 */
const prop = propOptions[key]
/* 父组件是否传入了该属性 */
const absent = !hasOwn(propsData, key)
/* 前key在propsData中对应的值,即父组件对于该属性传入的真实值*/
let value = propsData[key]
// boolean casting
/* 判断是不是布尔类型 */
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
/* 父组件没有传入该prop属性并且该属性也没有默认值的时候 */
value = false
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true
}
}
}
// check default value
/*
只需判断父组件是否传入该属性即可
*/
if (value === undefined) {
/*
父组件没有传入值的话,获取默认值
并且转化为响应式
*/
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
if (
process.env.NODE_ENV !== 'production' &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && ('@binding' in value))
) {
assertProp(prop, key, value, vm, absent)
}
return value
}
getPropDefaultValue
/*
获取props的默认值
*/
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
/*
没有定义default的话,就直接返回undefined
*/
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
/*
这里default的值,不能直接写成一个对象
默认对象或者数组,必须从一个工厂函数中获取
*/
if (process.env.NODE_ENV !== 'production' && isObject(def)) {
warn(
'Invalid default value for prop "' + key + '": ' +
'Props with type Object/Array must use a factory function ' +
'to return the default value.',
vm
)
}
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key]
}
/*
判断def是否为函数并且prop.type不为Function,
如果是的话表明def是一个返回对象或数组的工厂函数,
那么将函数的返回值作为默认值返回;如果def不是函数,那么则将def作为默认值返回
*/
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
initMethods
/*
初始化方法
*/
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
/* 开始遍历methods */
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
/* 如果methods不是函数的话,就报错 */
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
/* methods如果跟props重名,就报错 */
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
// 如果在实例中已经存在,并且方法名是以_或$开头的,就抛出错误
// isReserved函数是用来判断字符串是否以_或$开头
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
初始化函数定义位于源码的src/core/instance/state.js中
/*
初始化 data
1. 判断data 是否合法
2. 转化成响应式
3. 绑定到vm实例上,也就是proxy代理
*/
function initData (vm: Component) {
/* 获取data,建议使用函数的形式,挂载到_data上 */
let data = vm.$options.data
/*
如果是工厂函数,就执行,获取返回值
*/
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
/*
判断是不是一个对象,否则就报错
data函数的返回值需要是一个对象,否则就会报错
无论传入的data选项是不是一个函数,它最终的值都应该是一个对象
*/
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
/*
下面是用来判断,data ,props, 还有methods 不能重名
*/
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
/* 是否跟methods重名 */
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
/* 是否跟props重名 */
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
/* 判断key不是 _或者$ 开头的属性 */
/*
代理key
this.key === this._data.key
*/
proxy(vm, `_data`, key)
}
}
/*
观察者 到了
把data 里面的属性转化为响应式
*/
observe(data, true /* asRootData */)
}
/*
getData 执行传入的函数,返回值
*/
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
initComputed
/*
计算属性的初始化
计算属性的结果会被缓存
*/
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
/*
开始遍历computed里面的每一项属性
*/
for (const key in computed) {
// 拿到key对应的值
const userDef = computed[key]
// 判断是不是函数,当然也可以是对象,平时写函数是比较多的
/* 如果不是函数,就吧对象里的get返回 */
const getter = typeof userDef === 'function' ? userDef : userDef.get
/* 这里正常来说getter 是个undefined,但是用了两个等号,所以undefined == null 为TRUE */
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
// 这个位置是关键
if (!isSSR) {
// create internal watcher for the computed property.
/*
创建一个watcher实例
并且放到对象watchers上面
*/
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// 这里的意思是computed里面定义的key的名字是不能跟data或者props里面的key冲突的
/*
判断这个key在不在实例上面
*/
if (!(key in vm)) {
// 下面进入关键代码
// 为实例vm上设置计算属性
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
/* 下面三个判断就是判断在具体的哪个地方定义了相同的名字 */
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(`The computed property "${key}" is already defined as a method.`, vm)
}
}
}
}
defineComputed
// 作用是为target上定义一个属性key,并且属性key的getter和setter根据userDef的值来设置
/*
给实例vm上设置计算属性
target , vm 实例
key: computed的属性
userDef: computed的值
*/
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 判断是否应该有缓存,只有在非服务器下,才是TRUE
/* 只有在非服务器环境下才会有缓存 */
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
// sharedPropertyDefinition 是一个默认的属性描述符
// 也就是说,在浏览器环境下,走了 createComputedGetter 函数
// 因为userDef只是一个普通的getter,它并没有缓存功能,
// 所以我们需要额外创建一个具有缓存功能的getter, createComputedGetter
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
/* userDef为对象 */
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
// 属性key绑定到target上,其中的属性描述符就是上面设置的sharedPropertyDefinition。
// 如此以来,就将 计算属性 绑定到实例 vm 上了
// 属性key绑定到target上
Object.defineProperty(target, key, sharedPropertyDefinition)
}