vue源码解读(二) -- new vue(options)的过程

236 阅读4分钟

new Vue(options) 发生了什么?

new Vue(options)调用initMixin()函数中的_init函数进行一系列的初始化过程,调用的相关函数如下图: initMixin.png Vue 的构造函数在 /src/core/instance/index.js 文件中,下面来对相关函数进行解读

// Vue 的构造函数 
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') 
    } 
    // 调用 Vue.prototype._init 方法,该方法在 initMixin 中定义
    this._init(options) 
} 
// 定义 Vue.prototype._init 方法
initMixin(Vue) 
export default Vue

initMixin

定义了 Vue.prototype._init 方法,执行 new Vue() 时,会调用 _init 方法来实现一系列初始化操作,包括整个生命周期的流程、响应式系统流程的启动等

export function initMixin (Vue: Class<Component>) { 
    // 负责 Vue 的初始化过程
    Vue.prototype._init = function (options?: Object) {       
        // vue里把vue实例都叫做vm 
        const vm: Component = this 
        // 每个 vue 实例都有一个 _uid,并且是依次递增的 
        vm._uid = uid++ 
        // 避免自身被观察的标志 
        vm._isVue = true 
        // 合并选项 
        if (options && options._isComponent) { 
            // 每个子组件初始化时走这里,这里只做了一些性能优化 
            // 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率 
            initInternalComponent(vm, options) 
        } else { 
            // 根组件走这里:选项合并,将全局配置选项合并到根组件的局部配置上 // 组件选项合并,其实发生在三个地方: 
            // 1. Vue.component(CompName, Comp) 做了选项合并,合并的 vue 内置的全局组件 
            // 和用户自己注册的全局组件,最终都会放到全局的 components 选项中 
            // 2. { components: { xxx } } 全部注册,执行编译器生成的 render 函数时做了合并 // 会合并全局配置项到组件局部配置项上 
            // 3. 这里的根组件的情况了 
            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 
        // 重点,整个初始化最重要的部分,也是核心 
        // 初始化vm实例中和生命周期相关的属性,设置了vm的 $parent,$root,$children, 
        // $refs,_watcher,__isMounted,_isDestroyed,_isbeing,Destroyed的默认值 
        initLifecycle(vm) 
        // 事件监听初始化 
        // 存储父组件绑定当前子组件的事件,保存到vm._events中 
        initEvents(vm) 
        // 初始化插槽,获取this.$slots, 
        // 定义 this._c, 即createElement方法,也就是平时使用的 h 函数 
        initRender(vm) 
        // 从vm.$options中拿到beforeCreate的钩子方法数组, 
        // 传入vm对象作为上下文(this),循环执行 
        callHook(vm, 'beforeCreate') 
        // 初始化 inject 选项,得到 result[key] = val 形式的配置对象,并作响应式处理 
        initInjections(vm) 
        // resolve injections before data/props 
        // 响应式原理的核心,对prop,method,data,computed,watch的初始化 
        initState(vm) 
        // 设置 vm._provided值,以便子组件逐级查找 
        // inject与provide配合实现了属性的多级传递 
        initProvide(vm) 
        // resolve provide after data/props 
        // 调用 created 声明周期钩子函数 
        callHook(vm, 'created') 
        // 如果存在 el 选项,自动执行 $mount 
        if (vm.$options.el) { 
            vm.$mount(vm.$options.el) 
        } 
    } 
}

initLifecycle

初始化了 $parent、$root、children、$refs、_watcher、_isMounted、_isDestroyed、_isBeingDestroyed 等函数,该方法只是在Vue的私有属性上添加了默认值

export function initLifecycle (vm: Component) { 
    const options = vm.$options 

    // locate first non-abstract parent 
    let parent = options.parent 
    if (parent && !options.abstract) { 
        while (parent.$options.abstract && parent.$parent) { 
            parent = parent.$parent 
        } 
        parent.$children.push(vm) 
    } 
    
    vm.$parent = parent 
    vm.$root = parent ? parent.$root : vm 
    vm.$children = [] 
    vm.$refs = {} 
    vm._watcher = null 
    vm._inactive = null 
    vm._directInactive = false 
    vm._isMounted = false 
    vm._isDestroyed = false 
    vm._isBeingDestroyed = false 
}

initEvents

初始化一个存放事件的空对象,只存放挂载在该组件上的事件,_hasHookEvent属性表示父组件是否将钩子函数绑定到该组件上,如果父组件绑定事件到该组件上则调用updateComponentListeners方法,最终调用updateListeners更新Listener

export function initEvents (vm: Component) { 
    vm._events = Object.create(null) 
    vm._hasHookEvent = false 
    // init parent attached events 
    const listeners = vm.$options._parentListeners 
    if (listeners) { 
        updateComponentListeners(vm, listeners) 
    } 
}

initRender

初始化渲染

  • 将组件的插槽编译成虚拟节点 DOM 树, 以列表的形式挂载到 vm 实例,初始化作用域插槽为空对象
  • 将模板的编译函数(把模板编译成虚拟 DOM 树)挂载到 vm 的 _c 和 $createElement 属性
  • 最后把父组件传递过来的 $attrs$listeners 定义成响应式的
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

callHook

完成渲染初始化,依次调用 beforeCreate、created 钩子函数

用户使用的 beforeCreate 、 created 等钩子在 Vue 中是以数组的形式保存的,可以看成是一个任务队列。 即每个生命周期钩子函数都是 beforeCreate:[fn1, fn2, fn3, ... , fnEnd] 这种结构, 当调用 callHook(vm, 'beforeCreate') 时, 以当前组件的 vm 为 this 上下文依次执行生命周期钩子函数中的每一个函数

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

pushTarget

pushTarget()函数的作用是往 targetStack 数组尾部压入(保存) Watcher 对象,并把对象赋值给 Dep.target。

popTarget

popTarget()函数的作用是把 targetStack 数组尾部最后一个 Watcher 对象弹出(删除),然后把数组最后一个对象赋值给 Dep.target

initProvide

将 provide 的对象设置到 Vue 实例的 _provided 属性上,resolveInject 方法用于解析 inject 的数据,return 的数据是对象类型

initInjections

inject 值的逐级查找和借助 defineReactive() 方法将查找到的 inject 值定义到当前的组件实例上

export function initInjections (vm: Component) {
  // 从配置项上解析 inject 选项,最后得到 result[key] = val 的结果
  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 {
        // 对解析结果做响应式处理,将每个 key 代理到 vue 实例上
        // 我们才能使用 this.key 这种方式
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

initState

对prop,method,data,computed,watch初始化,将prop,method,data,computed,watch等属性代理到vm实例上

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

为 props 对象的每个属性设置响应式(for...in 遍历props中的属性,调用defineReactive(props, key, value)方法实现响应式),并将其代理到 vm 实例上

initMethods

将 methods[key] 放到 vm 实例上,得到 vm[key] = methods[key]

initData

代理 data 对象上的属性到 vm 实例,为 data 对象的上数据设置响应式

initComputed

为 computed[key] 创建 watcher 实例,默认是懒执行,代理 computed[key] 到 vm 实例

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()
    
  // 循环遍历,为每个 computed 配发 watcher
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // 每个 computed 都创建一个 watcher 
      // watcher 用来存储计算值,判断是否需要重新计算
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        { lazy: true }
      )
    }
    // 判断是否有重名的属性
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    }
  }
}

initWatch

遍历 watch 对象,调用 createWatcher 函数

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

observe

响应式处理的真正入口,为对象创建观察者实例,如果对象已经被观察过,则返回已有的观察者实例,否则创建新的观察者实例,底层调用defineReactive函数(Object.defineProperty)实现对数据的监听。