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函数,首先去判断是否有mixin和extends,如果有,则递归调用自身。然后通过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)
}
}
组件插入的顺序是先子后父,所以会先子后父的执行insert,insert是在组件初始化的时候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
}
}