每个 Vue 实例在被创建时都要经过一系列的初始化过程,例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
生命周期图示
生命周期钩子
所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算
beforeCreate
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
<div id="app">
<div @click="handleClick">点击事件</div>
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
beforeCreate () {
console.log(this.msg); // undefined
console.log(this.handleClick); // undefined
console.log('-----beforeCreate-----');
},
methods: {
handleClick () {
console.log(handleClick);
}
},
watch: {
msg: {
handler () {
console.log('侦听msg的值');
},
immediate: true,
}
}
})
打印顺序:
undefined
undefined
-----beforeCreate-----
侦听msg的值
created
在实例创建完成后被立即调用。
在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
如果要在第一时间调用 methods 中的方法,或者操作data中的数据,可在此钩子中进行操作。 需要注意的是,执行此钩子时,挂载阶段还未开始,$el 属性目前不可见。
此时,可以进行数据请求,将请求回来的值赋值给data中的数据。
<div id="app">
<div @click="handleClick">点击事件</div>
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
created () {
console.log(this.msg); // hello world
console.log(this.handleClick); // function () {...}
console.log(this.$el); // undefined
console.log('----------created-------');
},
methods: {
handleClick () {
console.log(handleClick);
}
},
watch: {
msg: {
handler () {
console.log('侦听msg的值');
},
immediate: true,
}
}
})
打印顺序:
侦听msg的值
hellow world
ƒ handleClick () { console.log(handleClick); }
undefined
----------created-------
beforeMount
在挂载开始之前被调用,此时模板已经编译完成,只是未将生成的模板替换el对应的元素。
在此钩子函数中,可以获取到模板最初始的状态。
此时,可以拿到vm.$el,只不过为旧模板
const vm = new Vue({
el: '#app',
beforeMount () {
console.log(this.$el);
}
})
mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
在该钩子函数中的 vm.$el 为新模板。
执行完该钩子函数后,代表实例已经被完全创建好。
如果要在第一时间,操作页面上的 dom 节点时,可以在此钩子函数中操作
<div ref="container">
容器宽度:{{containerWidth}} 容器高度:{{containerHeight}}
</div>
export default {
data(){
return {
containerWidth:0,
containerHeight:0
}
},
mounted(){
this.containerWidth = this.$refs.container.clientWidth;
this.containerHeight = this.$refs.container.containerHeight;
}
}
beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁之前。此时数据已经更新,但是DOM还未更新
<div id="app">
{{ msg }}
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
beforeUpdate () {
console.log(this.msg);
console.log(this.$el);
},
methods: {
handleClick () {
console.log('handleClick');
}
}
})
this.msg = 'xxx';
updated
数据更改导致DOM重新渲染后,会执行该钩子函数。
此时数据和dom同步。
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
可以在该钩子函数中,清除定时器。
<div id="app">
{{ msg }}
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
timer: 0,
},
created () {
this.timer = setInterval(() => {
console.log('xxx');
}, 500)
},
beforeDestroy () {
clearInterval(this.timer);
}
})
destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除。
v-if 为 false 的时候组件会被销毁
new Vue
之后的流程
-
(实例被创建)首先做一些初始化的操作,主要是设置一些私有属性(以“_”和“$”开头的属性)到实例中
-
运行生命周期钩子函数
beforeCreate
-
进入注入流程:处理属性、computed、methods、data、provide、inject,最后使用代理模式将它们挂载到实例中
-
运行生命周期钩子函数
created
-
生成
render
函数:如果有配置,直接使用配置的render
,如果没有,使用运行时编译器,把模板编译为render
-
运行生命周期钩子函数
beforeMount
-
创建一个
Watcher
,传入一个函数updateComponent
,该函数会运行render
,把得到的vnode
再传入_update
函数执行。在执行
render
函数的过程中,会收集所有依赖,将来依赖变化时会重新运行updateComponent
函数在执行
_update
函数的过程中,触发patch
函数,由于目前没有旧树,因此直接为当前的虚拟dom树的每一个普通节点生成elm属性,即真实dom。如果遇到创建一个组件的vnode,则会进入组件实例化流程,该流程和创建vue实例流程基本相同,最终会把创建好的组件实例挂载vnode的
componentInstance
属性中,以便复用。 -
运行生命周期钩子函数
mounted
当数据发生改变时
-
数据变化后,所有依赖该数据的
Watcher
均会重新运行,这里仅考虑updateComponent
函数对应的Watcher
-
Watcher
会被调度器放到nextTick
中运行,也就是微队列中,这样是为了避免多个依赖的数据同时改变后被多次执行 -
运行生命周期钩子函数
beforeUpdate
-
updateComponent
函数重新执行在执行
render
函数的过程中,会去掉之前的依赖,重新收集所有依赖,将来依赖变化时会重新运行updateComponent
函数在执行
_update
函数的过程中,触发patch
函数。新旧两棵树进行对比。
普通
html
节点的对比会导致真实节点被创建、删除、移动、更新组件节点的对比会导致组件被创建、删除、移动、更新
当新组件需要创建时,进入实例化流程
当旧组件需要删除时,会调用旧组件的
$destroy
方法删除组件,该方法会先触发生命周期钩子函数beforeDestroy
,然后递归调用子组件的$destroy
方法,然后触发生命周期钩子函数destroyed
当组件属性更新时,相当于组件的
updateComponent
函数被重新触发执行,进入重渲染流程,和本节相同。 -
运行生命周期钩子函数
updated