源码笔记(四):created 阶段
接上文继续分析,在触发生命周期钩子 beforeCreate
后,执行:
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
initInjections
依赖注入,用于层级组件间传值,不可响应。
判断是否存在 $options.inject
,然后在 resolveInject
里递归向上各级父元素中查找 vm._provided
属性值里是否有对应的注入值。找到最新值后放在实例下监听, set
方法设置无法重写,即不能更改注入值。
initState
function initState(vm) {
vm._watchers = [];
var 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);
}
}
依次对用户提供的 options
的字段进行初始化处理,会判断字段名是否合法等。顺序是:props,methods,data,computed,watch
。
props
如果是子组件的 props
,则对每一项 propsOptions
执行 loop
,方法里将 propsOptions
的 key
存入 vm.$options._propKeys
,验证 props
类型,然后将每一项 props
存入 vm._props
里并监听,set
方法设置无法重写,即不能更改注入值。
methods
其中初始化 methods
会把方法验证后,依次附到 Vue
实例 vm
下。
data
如果不是组件,其中初始化 data
会执行 getData() => data() => mergedInstanceDataFn()
,即在 mergeOptions
方法里 strats
处理 data
的时候返回的 mergedInstanceDataFn
。在其中 mergeData
了父 data
和自己的 data
,然后返回。
如果是组件,则在执行 data()
即执行传入的 options
里的 data
函数,然后返回 data
对象值并赋给 _data
。所以须保证 data
为函数返回一个新的对象,否则如果在模板中多次声明同一组件,组件中的 data
会指向同一个引用。
然后对 data
中每一项进行了合法性判断,然后执行 proxy(vm, "_data", key)
,将 key
挂载在 vm
实例上,监听 data
第一层的所有属性。保证在读取或者设置 vm.someData
时会触发监听执行 return vm['_data']['somedata']
或 vm['_data']['somedata'] = val
,进而触发下文 data
上对每一个属性的监听。
循环执行完后,然后执行:
observe(data, true /* asRootData */); //即 _data
方法里先判断 data
里 __ob__
是否存在,如果不存在则执行:
var Observer = function Observer(value) {
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); //递归遍历监听对象的每一个属性
}
};
该方法里对每一个属性及其子属性递归初始化订阅者列表,当读取到某一属性时,就会把当前 watcher 加入到该属性的订阅列表里,当设置某一属性时,就会去通知订阅列表里所有的 watcher 执行其对应的 update 方法。
__ob__
__ob__
为 Observer
实例,每一个被监听的对象(包括数组)下都会拥有一个 __ob__
属性,内部保存着 data
值和该 data
的订阅列表。在 reactiveGetter
里可以对其添加订阅:
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value); // 循环给数组的每一项添加订阅
}
}
protoAugment
其中,对应数组类型的监听执行 protoAugment
,改写 value
(数组类型)的 __proto__
为 arrayMethods
,这样在执行数组方法的时候,就会被拦截执行 arrayMethods
对象里重写后的变异方法,在变异方法里触发通知订阅该数组订阅的列表。
检测变化的注意事项(来自文档)
- 对于对象:
Vue
无法检测property
的添加或移除。由于Vue
会在初始化实例时对属性执行getter/setter
转化,所以属性必须在data
对象上存在才能让Vue
将它转换为响应式; - 对于数组:
Vue
无法监听通过索引(即下标)设置数组项(性能原因)和修改数组的长度(length
属性无法设置get/set
)。
computed
其中对 computed
里的每一项判断是否是 SSR
后,执行:
watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions);
其中 watchers
即 vm._computedWatchers
,这里实例化了一个 计算 watcher
,其中 computedWatcherOptions
为 {lazy: true}
,所以不会立即得到值,进而不会触发 get
增加订阅。然后执行:
defineComputed(vm, key, userDef);
方法里将 key
挂载在实例下监听,get
方法为 computedGetter
(执行 createComputedGetter(key)
(柯里化)的结果),set
方法设置无法重写。
watch
其中对 watch
里的每一项执行:
createWatcher(vm, key, handler);
即执行 vm.$watch(expOrFn, handler, options)
,方法里会执行 new Watcher(vm, expOrFn, cb, options)
,其中 expOrFn:'info','cb':'handler','options':'{handler: handler,deep: true,user: true}'
。其中 user: true
表示用户自己写的 watcher
。
watcher
里执行 this.get->this.getter
方法取 value
时,会触发之前监听该变量的 proxyGetter
,然后将该 watcher
订阅到对应变量上(watcher
push
到 Dep
的实例属性 subs
里),当变量后续变化时就会通知 subs
里的 watcher
列表做更新操作。
也就是说,在执行 expression
获取对应 value
的过程中,expression
里涉及到哪个已经被监听的属性,都会给该属性添加本 watcher
,即订阅。当变量后续变化就会来通知该 watcher
执行 update
操作。
此时,因为 deep: true
,所以在 Watcher.prototype.get
里的 finally
里执行 traverse(value)
,递归读取 value
对象(包含数组)的每一个属性,即会触发监听每个变量的 proxyGetter
,将该watcher
订阅到所有变量上。
判断有无 immediate
控制是否立即执行后,返回 unwatchFn
提供销毁该 watcher
的执行方法。
initProvide
判断是否存在 $options.provide
,将其赋到 vm._provided
属性上。
触发 created 钩子
callHook(vm, 'created');
执行生命周期钩子 created
,打印 vue created
。异步数据读取可在此钩子执行。
本章小结
- 本章介绍了
vue
执行的created
阶段; - 初始化了依赖注入
inject/provide
相关; - 对
options
里的props,methods,data,computed,watch
属性进行处理,做一些监听,绑定,订阅之类的操作,包括数组的监听等等。