源码笔记(四):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属性进行处理,做一些监听,绑定,订阅之类的操作,包括数组的监听等等。