详解Vue2中的生命周期与钩子函数(Vue面试官必问系列)
目录
一、概念
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数。生命周期钩子的 this
上下文指向调用它的 Vue 实例。
钩子函数是用于描述一个组件实例从引入到退出的全过程中的某个过程,整个过程成为生命周期。
也就是在如下代码中,new一个Vue实例的这几行代码,所执行的全过程。
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
二、生命周期过程
1、流程图示
2、三阶段
组件的生命周期共分为三个阶段:挂载阶段、更新阶段、销毁阶段。
每个阶段对应的钩子函数分别为:
- 挂载阶段:beforeCreate、created、beforeMount、mounted
- 更新阶段:beforeUpdate、updated
- 销毁阶段:beforeDestroy、destroyed
即首次创建实例过程中,只执行四个生命周期钩子,beforeCreate、created、beforeMount、mounted,执行顺序与生命周期图一致,beforeUpdate与updated是在数据更新时候执行,而beforeDestroy与destroyed是在实例销毁的时候执行。
三、钩子函数详解
new (创建)一个Vue实例,会调用初始化 init 函数,初始化事件(如$once)和生命周期,注意此时数据代理并没有实现。此时,无法通过Vue实例来访问data中的数据、computed、watch、methods等中的方法。
1. beforeCreate() 创建前
beforeCreate() 创建前,指的是数据监测和数据代理创建之前。该钩子函数执行后,初始化数据,并通过Object.defineProperty()和给组件实例配置watcher观察者实例(发布-订阅者模式),实现数据监测与数据代理。
案例验证:
new Vue({
el:"#app",
render: h => h(App),
data() {
return {
demo:"hello world!"
}
},
beforeCreate(){
console.log("****beforeCreate()执行阶段*****")
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el)
}
})
打印结果如下:证明此时,data中的数据与el还未初始化,无法通过Vue实例来访问data中的数据、computed、watch、methods等中的方法。
2. created()创建后
created()创建后,指的是数据监测和数据代理创建之后。该钩子函数执行后,实例创建完成,实例已完成以下配置:数据观测、属性和方法的运算,watch/event事件回调,完成了data 数据的初始化,可以访问data、computed、watch、methods上的方法和数据。但是,未挂载到DOM,不能访问到el属性,el属性,ref属性内容为空。
补充:ref属性介绍
1. 被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:
1. 打标识:<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>
2. 获取:this.$refs.xxx
案例验证:
new Vue({
el:"#app",
render: h => h(App),
data() {
return {
demo:"hello world!"
}
},
beforeCreate(){
console.log("****beforeCreate()执行阶段*****")
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el)
},
created(){
console.log("****created()执行阶段*****")
console.log(this)
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el);
debugger;
}
})
打印结果如下:证明此时,data 数据的初始化完成,数据监测和数据代理创建,el目前不可见
这个钩子函数比较重要,这个时候类似于人类的婴儿刚刚出生,这时候,需要给婴儿做一些检查。所以,在这个时候,可以调用methods中的方法,改变data中的数据且变化可以响应在页面上,获取computed中的计算属性等等,也可以在这里发ajax请求等(给婴儿的检查就类似调用方法,发送请求等)。
created执行完毕后,进行下面的环节:
在分析这个图示之前,我们先展示new Vue实例的两种方式:
方式一:含有el配置项
new Vue({
el: '#app', //含有el配置项
router,
store,
render: h => h(App)
})
方式二:无 el 配置项,调用$mount进行挂载
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
现在分析图示:new Vue实例时,是否有 el 配置项,假如采用方式一创建Vue实例,则有 el 配置项,则进一步判断是否有 template 配置项;假如采用方式二创建Vue实例,则无 el 配置项,当执行.$mount('#app')时,则进一步判断是否有 template 配置项。
有 template 配置项时,将template模板转换成render函数(在此之前判断当前是否有render函数,如果有,则会直接去渲染当前的render函数,如果没有则查找是否有template模板),如果没有template,将直接获取到的el(也就是我们常见的#app)的outerHTML的内容编译成template,然后在将这个template转换成render函数。
在上述图示的环节中,就是Vue解析模板template的过程,此时,在内存中生成虚拟DOM,但是页面不能显示解析好的内容。
3. beforeMount() 挂载前
beforeMount() 执行时,页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作,最终都不奏效。
案例验证:
new Vue({
el:"#app",
render: h => h(App),
data() {
return {
demo:"hello world!"
}
},
beforeCreate(){
console.log("****beforeCreate()执行阶段*****")
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el)
},
created(){
console.log("****created()执行阶段*****")
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el)
},
beforeMount() {
console.log("****beforeMount()执行阶段*****")
debugger
},
})
断点放在在beforeMount()执行阶段,结果如下:
从下图可以看到,页面呈现的是未经Vue编译的DOM结构
接下来的环节:
这个环节,将内存中的虚拟DOM,转换成真实DOM,插入到页面中,注意,$el中也存储了真实DOM,并不是直接仅仅插入到页面中。这个环节为后续的diff算法提供了基础。
案例验证:将断点放在mounted()执行阶段
new Vue({
el:"#app",
render: h => h(App),
data() {
return {
demo:"hello world!"
}
},
beforeCreate(){
console.log("****beforeCreate()执行阶段*****")
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el)
},
created(){
console.log("****created()执行阶段*****")
console.log("demo 的值是:",this.demo)
console.log("el 的值是:",this.$el)
},
beforeMount() {
console.log("****beforeMount()执行阶段*****")
},
mounted(){
console.log("****mounted()执行阶段*****")
debugger
}
})
此时页面上呈现的是经过Vue编译的内容,如下:
4. mounted()挂载完成
此时,页面上呈现的是经过Vue编译的DOM;对DOM的操作均有效(但是要尽量避免操作DOM)。
至此,初始化阶段全部完成。一般在此执行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等初始化操作。
5. beforeUpdate() 更新前
当数据发生变化,执行beforeUpdate()钩子函数,此时,内存中数据是新的,但是页面是旧的,也就是,在这个钩子函数中,页面和数据不同步
案例验证:
data() {
return {
n:1
}
},
methods: {
updateN(){
this.n += 1
}
},
beforeUpdate(){
console.log("****beforeUpdate()执行阶段*****")
console.log("n的值为",this.n)
debugger
},
当点击按钮,内存中的数据发生变化,n=2,但是页面上n仍然为1:
下图所示的环节:
beforeUpdate执行结束之后,重新生成一个新的虚拟dom(Vnode),用它和原来的Vnode做比较(diff算法)patch指的就是这个比较的过程,更新render函数中的数据,之后将render函数渲染成真实dom,完成了 Model --> View 的更新。
6. updated() 更新后
此时:内存中数据是新的,但是页面是新的,也就是,在这个钩子函数中,页面和数据保持同步
beforeUpdate(){
console.log("****beforeUpdate()执行阶段*****")
console.log("n的值为",this.n)
},
updated(){
console.log("****updated()执行阶段*****")
console.log("n的值为",this.n)
debugger
}
代码的执行结果如下:
接下来的环节: 当调用$.destroy()时,会完全销毁实例
7. beforeDestroy() 销毁前
在销毁前,实例中所有的data、methods、computed、指令等,都处于可用状态在此阶段,一般进行:关闭定时器、取消订阅消息解绑自定义事件等收尾工作。
接下来的环节,移除监视、所有的子组件、(自定义)事件的监听器
8. destroyed() 销毁完成
销毁完成后,执行destroyed。该实例的生命周期结束。
三、生命周期流程与各流程详解图示(非常实用)
* 注:该图片来源《尚硅谷Vue技术全家桶(天禹老师主讲)的上课资料》
四、父子组件钩子函数在三个阶段的代码执行顺序
挂载:父亲created> 子created > 子mounted> 父亲mounted>
更新:父亲beforeUpdate > 子beforeUpdated > 子updated > 父亲updated
销毁:父亲beforeDestroy> 子beforeDestroy > 子destroyed> 父destroyed
更多推荐
所有评论(0)