vue源码分析(五)

228 阅读3分钟

3.配置合并

mixin方法实际是通过mergeOptions方法把传入的配置,和本身的options做一个合并,往Vue的options上做一些扩展

// src/core/global-api/mixin.js
import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

在触发_init的时候,vue做了相关的合并options的处理,分为组件初始化new Vue初始化两种合并方式

 Vue.prototype._init = function (options?: Object) {
    // 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
      )
    }
  }

3-1 new vue 初始化

对于new Vue时的初始化mergeOptions的第一个参数是resolveConstructorOptions(vm.constructor),对于首次new Vue,Vue是没有super的,所以直接返回Vue.options

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 传入的Ctor是Vue
  if (Ctor.super) { 
   ...
  }
  return options
}

Vue的options在initGlobalAPI时初始化

// src/core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) {
	...
	Vue.options = Object.create(null)
  	ASSET_TYPES.forEach(type => {
    	  Vue.options[type + 's'] = Object.create(null)
  	})
     	// this is used to identify the "base" constructor to extend all plain-object
  	// components with in Weex's multi-instance scenarios.
  	Vue.options._base = Vue

  	extend(Vue.options.components, builtInComponents)
    	...
}

mergeOptions函数,首先去判断是否有mixinextends,如果有,则递归调用自身。然后通过mergeField方法进行合并

// src/core/util/options.js
/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  ...
  if (typeof child === 'function') {
    child = child.options
  }
  ...
  // 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) {
    // 合并extends和mixins
    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
}

strats里定义了data,生命周期等不同的合并策略,比如生命周期部分。mergeHook是生命周期的合并方式,它返回一个函数数组。

// src/shared/constants.js
export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured',
  'serverPrefetch'
]
// src/core/util/options.js
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})
...
/**
 * Hooks and props are merged as arrays.
 */
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

3-2 组件初始化

组件初始化会执行initInternalComponent(vm, options)

// src/core/instance/init.js
export 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
  }
}

vm.constructor.options是在子组件执行extends生成的时候初始化的,拿到了Vue.options和当前你的options

Vue.extend = function (extendOptions: Object): Function {
    ...
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    ...
}

4.生命周期

callHook函数是执行生命周期的函数接受两个参数,第一个参数是当前的vm实例,第二个参数一个string类型,代表要触发的生命周期的名称。首先从 vm.$options[hook]拿到对应的生命周期,然后触发invokeWithErrorHandling函数

// src/core/instance/lifecycle.js
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()
}

invokeWithErrorHandling函数通过call和apply来执行对应vm实例的生命周期

// src/core/util/error.js
export function invokeWithErrorHandling (
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  ...
  res = args ? handler.apply(context, args) : handler.call(context)
  ...
}

callHook(vm, 'beforeCreate')执行之之后,会调用initState初始化data,所以在created的时候可以访问到this.data

// src/core/instance/init.js
 Vue.prototype._init = function (options?: Object) {
 	...
 	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')
	...
 }

mountComponent执行后,首先触发beforeMount函数

// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
	...
	callHook(vm, 'beforeMount')
    	...
    	let updateComponent
        ...
        updateComponent = () => {
      	   vm._update(vm._render(), hydrating)
    	}
        ...
         new Watcher(vm, updateComponent, noop, {
            before () {
              if (vm._isMounted && !vm._isDestroyed) {
                callHook(vm, 'beforeUpdate')
              }
            }
          }, true /* isRenderWatcher */)
        hydrating = false

        // manually mounted instance, call mounted on self
        // mounted is called for render-created child components in its inserted hook
        if (vm.$vnode == null) {
          vm._isMounted = true
          callHook(vm, 'mounted')
        }
}

执行mounted的有两处

  • 如果渲染的是根节点,$vnode是null,也就是不存在父vnode,在渲染完之后会执行mounted
// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  ...
  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
}
  • 如果是组件,在执行patch的最后会调用invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch),他会遍历insertedVnodeQueue,执行insert方法
// src/core/vdom/patch.js
function invokeInsertHook (vnode, queue, initial) {
  // delay insert hooks for component root nodes, invoke them after the
  // element is really inserted
  if (isTrue(initial) && isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}

insertedVnodeQueue是在patch过程当中是不断添加的,第一处,是在执行createElm的时候,如果定义了data那么会执行invokeCreateHooks(vnode, insertedVnodeQueue)

// src/core/vdom/patch.js
function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
      ...
      /* istanbul ignore if */
      if (__WEEX__) {
        ...
      } else {
        ...
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        ...
      }
      ...
  }

invokeCreateHooks函数会判断hook中是否定义了insert,如果定义,那么把vnode往insertedVnodeQueue中push

// src/core/vdom/patch.js
function invokeCreateHooks (vnode, insertedVnodeQueue) {
 ...
  i = vnode.data.hook // Reuse variable
  if (isDef(i)) {
    if (isDef(i.create)) i.create(emptyNode, vnode)
    if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
  }
}

第二处是在执行createComponent的时候会执行initComponent(vnode, insertedVnodeQueue),往insertedVnodeQueue中push vnode

// src/core/vdom/patch.js
function initComponent (vnode, insertedVnodeQueue) {
    if (isDef(vnode.data.pendingInsert)) {
      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
      vnode.data.pendingInsert = null
    }
    vnode.elm = vnode.componentInstance.$el
    if (isPatchable(vnode)) {
      invokeCreateHooks(vnode, insertedVnodeQueue)
      setScope(vnode)
    } else {
      // empty component root.
      // skip all element-related modules except for ref (#3455)
      registerRef(vnode)
      // make sure to invoke the insert hook
      insertedVnodeQueue.push(vnode)
    }
  }

组件插入的顺序是先子后父,所以会先子后父的执行insertinsert是在组件初始化的时候installComponentHooks绑定的,insert中会执行mounted,所以组件的mounted是先子后父的

// src/core/vdom/create-component.js
const componentVNodeHooks = {
    ...
    insert (vnode: MountedComponentVNode) {
      const { context, componentInstance } = vnode
      if (!componentInstance._isMounted) {
        componentInstance._isMounted = true
        callHook(componentInstance, 'mounted')
      }
      ...
    },
    ...
}

boforeUpdate定义在new Watcher渲染watcher的时候会传入before

// src/core/instance/lifecycle.js
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

在每一个nextTick执行flushSchedulerQueue的时候会执行watcher.before()触发beforeUpdate

// src/core/observer/scheduler.js
function flushSchedulerQueue () {
  ...
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }
  ...
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

callUpdatedHooks执行的时候会判断是否有_isMounted(是否mounted触发),如果触发过,那么执行callHook(vm, 'updated')触发updated

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'updated')
    }
  }
}

在组件销毁过程中会执行$destroy,首先执行callHook(vm, 'beforeDestroy'),触发beforeDestroy,然后执行一些系列销毁的执行,执行完之后,执行callHook(vm, 'destroyed')

// src/core/instance/lifecycle.js
 Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }