vue2.0源码-initMixin(二)

上次我们已经从总体上来看了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是用来判断开发者的当前环境是否支持SymbolReflect.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的实现方式。