生命周期就是从Vue实例创建、运行、到销毁的一个过程,在vue中提供了生命周期钩子的函数,可以在不同生命周期阶段添加自己的代码。
实例生命周期钩子
上面就是官网的图片,可以看到vue有beforeCreate,Created...等8个生命周期钩子,那么在执行到这些操作时,vue做了什么事情,我们可以从源码上看
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')
beforeCreate
在执行beforeCreate前有initLifecycle,initEvents,initRender三个函数
initLifecycle
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
}
基本上就是一下vm实例属性的初始化
initEvents
export function initEvents (vm: Component) {
vm._events = Object.create(null)//创建一个空对象,vm._events存放组件父组件绑定在当前组件上的事件
vm._hasHookEvent = false //表示的是父组件有没有直接绑定钩子函数在当前组件上
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)//更新事件
}
}
主要就是事件的注册
initRender
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)
}
}
貌似是创建虚拟节点的,=-=
可以看出在执行beforeCreate前vue只是做一些属性,事件的初始化,而data,props并没有做初始化,故而在这里是不能用this.data的
initInjections
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]) // getter setter
}
})
toggleObserving(true)
}
}
初始化 initInjections inject
initState
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) //初始化props
if (opts.methods) initMethods(vm, opts.methods) // 初始化methods
if (opts.data) {
initData(vm) //
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed) //computed初始化
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) // watch初始化
}
}
主要props,methods, data, computed,watch初始化
initProvide
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
可以看出在created可以获取props,methods, data,computed,watch
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
//略
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
//略
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating) //挂载节点
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
可以知道在beforeMount里真实的dom还没挂载所以不能用this.$refs.xx获取dom,并且是最后一次改this.data而不触发updated的生命周期,除此外beforeMount 到 mounted之间会触发子组件的生命周期。
test
//main.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld :msg="msg" ref="h"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
},
data() {
return{
msg:'Welcome'
}
},
beforeCreate() {
console.log(0,this.msg)
},
created() {
console.log(1,this.msg)
},
beforeMount() {
this.msg = 'ok'
console.log(3,this.$ref.h)
},
mounted() {
this.msg = 'ff'
console.log(4,this.$ref.h)
}
}
</script>
//HelloWorld.vue
<template>
<div class="hello">
{{test}}
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
computed:{
test(){
console.log(this.msg)
return this.msg
}
},
beforeCreate() {
console.log(2,'child created')
}
}
</script>