【Vue】Vue源码第三步——初始化(initMixin)

为了帮助更好理解,本人做了一个思维导图,大家可以搭配思维导图享用。
Vue初始化源码——思维导图

Vue创建实例

vue-dev\src\core\instance\index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

Vue实例的创建函数,然后调用_init方法,init方法在 ./init.js中实现

传入的options参数内容:

【Vue】Vue源码第三步——初始化(initMixin)_第1张图片


_init方法的实现代码

vue-dev\src\core\instance\init.js

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

初始化组件方法:
判断是否有option._Component,即当前Vue是否是组件实例,如果有,则根据option去初始化内部组件

第一个判断分支根据option是否是组件,进行分解步骤:
1、true,执行initInternalComponent(vm, options),第一个参数是当前vue实例,第二个参数是option

function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

初始化$option、父组件信息、render

2、false,合并option
resolveConstructorOptions(vm.constructor)
【Vue】Vue源码第三步——初始化(initMixin)_第2张图片
options
【Vue】Vue源码第三步——初始化(initMixin)_第3张图片

vm.$options = mergeOptions(
   resolveConstructorOptions(vm.constructor),
   options || {},
   vm
)

resolveConstructorOptions Vue的构造函数,是否有父类,如果有则一直循环父类缓存其父类属性。

export function resolveConstructorOptions (Ctor: Class) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

_init方法中的合并props、inject、directives

mergeOptions 方法在src\core\util\options.js
格式化props、inject、directives属性
如果有mixin或者extends等混合或者扩展,则继续合并选项

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

normalizeProps 判断是否有props属性,如果没有直接返回,如果有则判断是数组还是对象,然后遍历props转换成对象,赋值到props上。会把props连接字符串转换成驼峰字符串 time-end 变成 timeEnd

normalizeInject 判断是否有Inject属性,如果没有直接返回,如果有则根据对象或者数组形式用不同方法遍历并且组装成一个新的inject对象

{
	from : inject的name // 遍历数组或者对象中的名字
	defalut: inject对象的值  //如果inject是对象才会有这个属性
}

【Vue】Vue源码第三步——初始化(initMixin)_第4张图片
normalizeDirectives 格式化directives属性,没做什么特殊处理,如果你写属性的时候是如下写法,没有给指令指定钩子函数,那么就会默认添加到bind和update钩子上

directives: {
  focus: {
    function (el) {
      el.focus()
    }
  }
}

initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm) 
callHook(vm, 'created')

1、 initLifecycle(vm)
初始化一些私有属性和 p a r e n t 、 parent、 parentroot

2、initEvents(vm)
处理了listeners,其他没做啥事

3、initRender(vm)
创建 s l o t 、 slot、 slotscopeSlot,把 a t t r s 、 attrs、 attrslisteners变为响应式,注册render重要函数_c和$createElement

4、callHook(vm, ‘beforeCreate’)
执行beforeCreate生命周期钩子,判断是否有参数,通过call 和 apply执行的

5、initInjections(vm)
遍历injection,然后一层层往上找provide,找到对应的值返回,同时把inject变成响应式

6、initState(vm)
initData
设置_data,并注册成响应式,让我们可以在实例的方法(生命周期或其他api函数中)可以使用this.xxx访问到属性。
当我们使用this.xxx的时候,实际访问的是this[_data][xxx]

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : 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
  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)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      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)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

initProps
假设传入的props为 {data:0101}
1、缓存props的key
2、vm.$options.propsData 就是option中props的key和传入的props的值组成的对象 {data:0101}
3、validateProp(key, propsOptions, propsData, vm),参数1为props的key data,参数2是props的key对应的类型 data { type : null},参数3是 {data:0101},参数4是当前vue实例
4、校验props数据类型,并把props添加成响应式

function initProps (vm: Component, propsOptions: Object) {
  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
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    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
        )
      }
      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)
    }
  }
  toggleObserving(true)
}

initMethods
初始化option中的metho,把方法从当前实例的option.methods中直接挂载到当前实例上。如果methods里面定义了和props重复的方法,会报错;如果methods里面定义的方法和当前vue实例的选项有冲突,比如定义了一个叫mount的方法,会报错。

initDate
判断data的key在props、method里面是否有重复名字,有就报错,并且把data变成响应式

Observe
每个vue实例或者vue组件都有一个observe,初始化data的时候会把data进行监听

initComputed
获取computed,创建一个watcher观察者,缓存computed,把computed属性转换成响应式。如果data、props里面有和computed属性重名的,则报错

initWatch

==7、initProvide(vm) ==
挂载provide到当前实例上,变成_provided

8、callHook(vm, ‘created’)
回调生命周期函数,created

总结
Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。

你可能感兴趣的:(VUE)