Vue2.x(含组件)主流程源码笔记(四):created 阶段

381 阅读2分钟

接上文继续分析,在触发生命周期钩子 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,方法里将 propsOptionskey 存入 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 对象里重写后的变异方法,在变异方法里触发通知订阅该数组订阅的列表。

数组的 key

Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果不加 key,在数组顺序发生变化时,则因为同位置 vnode 相似(sameVnode),则会直接就地更新值,如果该项依赖子组件状态或临时 DOM 状态就会出现错误。加 keyvueupdateChildren 循环更准确。

但如果 key 就值为 index,则新旧 vnode 的每一项 key 都一样,那没有任何作用就跟没加一样,依旧是就地更新而不会找寻真正相同的节点。

检测变化的注意事项(来自文档)

  • 对于对象:Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式;
  • 对于数组:Vue 无法监听通过索引(即下标)设置数组项(性能原因)和修改数组的长度(length 属性无法设置 get/set)。

computed

其中对 computed 里的每一项判断是否是 SSR 后,执行:

watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions);

其中 watchersvm._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 pushDep 的实例属性 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

本章小结

  1. 本章介绍了 vue 执行的 created 阶段;
  2. 初始化了依赖注入 inject/provide 相关;
  3. options 里的 props,methods,data,computed,watch 属性进行处理,做一些监听,绑定,订阅之类的操作,包括数组的监听等等。