概述
前言:Vue实例有一个完整的生命周期,也就是说从开始创建、初始化数据、编译、挂载、更新、卸载、销毁等一系列的过程,许多的 钩子函数 便是从这一个个过程衍生而来,这里,我将其统称为Vue的生命周期。而钩子函数就是在生命周期运行的指定阶段给你一个做某些处理的机会。
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。—— Vue官方文档
生命周期图示
(图片来自Vue官网)
实例生命周期代码展示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vue生命周期探索</title>
</head>
<body>
<div id="app">{{count}}</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
count: 1
},
beforeCreate: function() {
console.log('Vue before create')
},
created: function() {
console.log('Vue created')
/* 这里设定1秒后更新data中的count */
setTimeout(() => {
console.log('更新data中的count')
this.count++
}, 1000)
/* 这里设定3秒后销毁Vue实例 */
setTimeout(() => {
console.log('销毁Vue实例')
this.$destroy()
}, 1000 * 3)
},
beforeMount: function() {
console.log('Vue before mount')
},
mounted: function() {
console.log('Vue mounted')
},
beforeUpdate: function() {
console.log('Vue before update')
},
updated: function() {
console.log('Vue updated')
},
beforeDestroy: function() {
console.log('Vue before destroy')
},
destroyed: function() {
console.log('Vue destroyed')
}
})
</script>
</body>
</html>
控制台输出结果:
实例生命周期详解
beforeCreate
(创建前)
在实例初始化之后,即new Vue()
之后,执行的第一钩子函数,触发钩子函数之后,Vue会开始对data
中的数据进行响应化以及内部事件的初始化等操作。此时组件的选项对象还未创建,el
和 data
并未初始化,因此无法访问methods
, data
, computed
等对象内的方法和数据。
created
(创建后)
在实例创建完成之后,即Vue对data
中的数据进行响应化以及内部事件的初始化等操作之后。此时,实例已经完成以下配置:
- Observe Data(对
data
中的数据进行响应化) - Init Events(内部事件初始化)
- watch/event事件回调
此时,你可以调用methods对象内的方法,改变data对象中的数据,发送Ajax请求......并且这些修改可以被Vue更新并体现在页面上。
beforeMount
(挂载前)
在实例被挂载到指定的DOM
之前被调用,此时,虚拟DOM树已经生成完毕,也可以理解为render()
方法首次调用之后。
mounted
(挂载后)
在实例被挂载到指定的DOM
之后被调用,也就是实例根据上一步生成的虚拟DOM,生成对应的HTML
内容,并且插入到指定的el
之后。
beforeUpdate
(更新前)
在数据更新之前被调用,此钩子函数发生在虚拟DOM重新渲染和页面对应的DOM替换之前,可以在此处更进一步地改变状态,并且不会导致重复渲染。
updated
(更新后)
在数据更新之后被调用,此钩子函数发生在虚拟DOM重新渲染和页面对应的DOM替换之后,此时,实例内的数据已经得到更新,页面上的DOM
元素也相应地替换完毕。
注意:大多数情况下,应该避免在此期间改变实例的状态(数据),因为这可能导致beforeUpdate
和updated
生命周期无限循环,进而造成代码阻塞页面卡死。(此钩子函数在服务端渲染期间不被调用)
beforeDestroy
(销毁前)
在实例销毁之前调用,此时实例内的所有的属性都还存在,可用性类似mounted
钩子函数,可以进行对应生命周期的所有操作。
通常在此钩子函数内会做这样一些操作:
- 清除组件中的定时器
- 清除附加给
DOM
元素的各种持久的事件监听
destroyed
(销毁后)
在实例销毁之后调用,此时,实例内部的所有特性都会被移除,所有的子实例也会被销毁。
注意:页面上的DOM
元素任然存在。(此钩子函数在服务端渲染期间不被调用)
路由钩子函数
全局级别
router.beforeEach
(前置守卫)
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
next();
});
router.beforeEach((to, from, next) => {
// ...
next();
});
你可以创建多个router.beforeEach
,当一个导航触发时,全局前置守卫会按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve
完(调用next()
)之前一直处于等待中。
每个前置守卫接收3个参数:(简记为:从哪来?到哪去?给不给过?)
to: Router
:即将要进入的目标路由对象from: Router
:当前导航正要离开的路由next: Function
:一定要调用该方法来resolve这个钩子,以告知Vue实例本次守卫的运行结束,并传递相应结果。
next方法调用的参数:
next()
:进行下一个router.beforeEach
钩子,如果router.beforeEach
钩子全部执行完了,则导航的状态就是有效的,浏览器也会按照预期跳转到to
路由对象对应的地址
next(false)
:中断当前导航,浏览器会保持/回到from
路由对象对应的地址。- next('/path')或者next({name: 'routerName'}):跳转到一个不同的地址。当前的导航会被中断,后续的守卫钩子不再执行,然后进行一个新的导航。
router.afterEach
(后置钩子)
router.afterEach((to, from) => {
// ...
})
你也可以创建全局后置钩子,然而和全局前置守卫不同的是,这些钩子不会接受next
函数,也不会改变导航本身。
页面级别
beforeRouterEnter
beforeRouterUpdate
beforeRouterLeave
export default {
data: function(){
return {
message: 'hello world'
}
},
beforeRouterEnter: function(to, from, next){
// 在渲染该组件的对应路由被 `confirm` 前调用
// 不能!不能!不能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouterUpdate: function(to, from, next){
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /page/:id,在 /page/1 和 /page/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouterLeave: function(to, from, next){
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
指令钩子函数
bind
只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted
被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
updated
所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
componentUpdated
指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind
只调用一次,指令与元素解绑时调用。
钩子函数参数
指令钩子函数会被传入以下参数:
{
el:'指令所绑定的元素,可以用来直接操作 DOM。'
binding:{/* 一个对象,包含以下属性: */
name:'指令名,不包括 v- 前缀。'
value:'指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。'
oldValue:'指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。'
expression:'字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。'
arg:'传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。'
modifiers:'一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。'
vnode:'Vue 编译生成的虚拟节点。'
}
oldVnode:'上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。'
代码展示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vue指令钩子函数探索</title>
</head>
<body>
<div id="app">
<p v-demo></p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script>
<script type="text/javascript">
Vue.directive('demo', {
bind: function(el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
const vm = new Vue({
el: '#app'
})
</script>
</body>
</html>
运行效果: