复习Vue11:生命周期探讨
其实在Vue
实例的生命周期,主要分为三个阶段,分别为
- 挂载(初始化相关的属性,例如
watch
属性,method
属性)
1、beforeCreate
2、created
3、beforMounte
4、mounted
- 更新(元素或组件的变更操作)
1、beforUpdate
2、updatad
- 销毁(销毁相关属性)
1、beforDestroy
2、destroyed
下面我们来看一道面试题:
关于Vue的生命周期,下列哪项是不正确的?()[单选题]
A、Vue 实例从创建到销毁的过程,就是生命周期。
B、页面首次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate, updated。
C、created表示完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来。
D、DOM渲染在mounted中就已经完成了。
分析:
选项A
是没有问题的,Vue
实例从创建到销毁的过程就是生命周期。
关于B
选项,我们可以通过写一个程序来进行验证。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期</title>
</head>
<body>
<div id="app">{{foo}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
foo: "foo",
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
</script>
<script></script>
</body>
</html>
上面的代码我们将所有的钩子函数都添加上了,我发现,当页面初次加载时所执行的函数钩子,并没有beforeUpdate
与updated
,所以选项B
是错误的
那么beforeUpdate
与updated
在什么时候会执行呢?是在组件或者是元素更新的时候。
下面,我们来测试一下,看一下效果
首先添加一个“更新”按钮
<div id="app">
{{foo}}
<button @click="update">更新</button>
</div>
对应的update
方法的实现:
methods: {
update: function () {
this.foo = "hello";
},
},
在update
方法中,修改了foo
属性的值。如下:点击了更新按钮
通过以上的测试,我们验证在更新元素的时候,会执行“更新”阶段的钩子函数。
下面我们测试一下,看一下“销毁阶段的钩子函数的执行”
<div id="app">
{{foo}}
<button @click="update">更新</button>
<button @click="destroy">销毁</button>
</div>
在上面的代码中增加了一个销毁的按钮,对应的destroy
的方法实现如下:
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
在destroy
方法中,调用了系统中的$destroy
方法销毁了所有的资源,这是会触发销毁阶段的钩子函数,所以这时会输出如下:
如果我们这是去点击更新按钮,就会发现什么效果都没有了,也就是无法完成元素的更新了,因为元素已经被销毁了。
下面,我们通过官方的生命周期图来再看一下整个生命周期的流程。也是为了看一下上面 C
和 D
的选项是否正确
beforcreate
:Vue
实例初始化之后,以及事件初始化,以及组件的父子关系确定后执行该钩子函数,一般在开发中很少使用
created
:在调用该方法之前,初始化会被使用到的状态,状态包括:props
,methods
,data
,computed
,watch
.
而且会实现对data
中属性的监听,也就是在created
的时候数据已经和data
属性进行了绑定。(放在data
中的属性当值发生改变的时候,视图也会发生改变)。同时也会传递到组件中的数据进行校验。
所以在执行created
的时候,所有的状态都初始化完成,我们也完全可以在该阶段发送异步的ajax
请求,获取数据。
但是在created
方法中,是无法获取到对应的$el
选项,也就是无法获取Dom
,所以说上题中的C
的说法是正确的。
如下代码所示:
created() {
console.log("created");
console.log("el===", this.$el);// undefined
console.log("data==", this.$data);// 可以获取数据
console.log("foo==", this.foo);//可以获取数据
},
created
方法执行完毕后,下面会判断对象中没有el
选项。如果有,继续执行下面的流程,也就是判断是否有template
选项,如果没有el
,则停止整个生命周期的流程,知道执行了vm.$mount(el)
后,才会继续向下执行生命周期的流程
下面我们测试一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期</title>
</head>
<body>
<!-- <div id="app">{{foo}}
<button @click="update">更新</button>
<button @click="destroy">销毁</button>
</div> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
// el: "#app", //去掉了el选项
data: {
foo: "fooData",
},
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
console.log("el===", this.$el);
console.log("data==", this.$data);
console.log("foo==", this.foo);
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
</script>
<script></script>
</body>
</html>
在上面的代码中,我们将el
选项去掉了,运行上面的代码后,我们发现执行完created
方法后,整个流程就停止了。
现在我们不添加el
选项,但是手动执行vm.$mount(el)
,也能够使暂停的生命周期进行下去。
如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期</title>
</head>
<body>
<div id="app">{{foo}}
<button @click="update">更新</button>
<button @click="destroy">销毁</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
// el: "#app", //去掉了el选项
data: {
foo: "fooData",
},
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
console.log("el===", this.$el);
console.log("data==", this.$data);
console.log("foo==", this.foo);
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
vm.$mount("#app");//添加了$mount方法
</script>
<script></script>
</body>
</html>
运行上面的代码,我们可以看到,虽然vm
对象中没有el
参数,但是通过$mount(el)
动态添加的方式,也能够使生命周期孙俪进行下去。
我们继续向下看,就是判断在对象中是否有template
选项。
第一:如果Vue
实力对象中有template
参数选项,则将其作为模板编译成render
函数,来完成渲染。
第二:如果没有template
参数选项,则将外部的html
作为模板编译(template
),也就是说,template
参数选项的优先级要比外部的html
高
第三:如果第一条,第二条都不具备,则报错
下面,我们看一看template
的情况
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期2</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app"></div>
<script>
const vm = new Vue({
el: "#app",
template: "<p>Hello {{message}}</p>",
data: {
message: "vue",
},
});
</script>
</body>
</html>
以上是在vue实例中添加template
的情况、
那么这里有一个比较有趣的问题就是,当模板同时放在template
参数选项和外部HTML
中,会出现什么情况呢?
如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期2</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<p>你好</p>
</div>
<script>
const vm = new Vue({
el: "#app",
template: "<p>Hello {{message}}</p>",
data: {
message: "vue",
},
});
</script>
</body>
</html>
上面的代码中,我们添加了template
属性,同时在外部添加了模板内容,但是最终在页面上显示的是Hello vue
而不是你好
。就是因为template
参数的优先级比外部的HTML
的优先级要高。
当然,我们在开发中,基本上都是使用外部的HTML
模板形式,因为更加灵活。
在这里,还需要你再次思考一个问题,就是为什么先判断el
选项,然后在判断template
选项呢?
其实通过上面的总结,我们是完全总结出来的。
就是因为Vue
需要通过el
的选择器找到对应的template
,也就是说,Vue
首先通过el
参数去查找对应的template
,如果没有找到template
参数,则到外部html
中查找,找到后将模板编译成render
函数(Vue
的编译实际上就是指Vue
把模板编译成render
函数的过程)。
下面,我们继续看一下生命周几的流程图。
接下来会触发 beforMount
这个钩子函数:
在执行该钩子函数的时候,虚拟DOM
已经创建完成,马上就要进行渲染了,在这里可以更改data
中的数据,不会触发updated
,其实在created
中也是可以更改数据,也不会触发updated
函数。
测试代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期</title>
</head>
<body>
<div id="app">{{foo}}
<button @click="update">更新</button>
<button @click="destroy">销毁</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
// el: "#app", //去掉了el选项
data: {
foo: "fooData",
},
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
console.log("beforeMount el===", this.$el);
console.log("data==", this.$data);
//this.foo = "abc"; //修改数据
console.log("foo==", this.foo);
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
vm.$mount("#app");//添加了$mount方法
</script>
<script></script>
</body>
</html>
通过上面的代码,我们可以获取el
中的内容,同时也可以修改数据。
但是,这里需要注意的输入的el
中的内容,{{foo}}
还没有真正的数据替换掉。而且对应的内容还没有挂载到页面上。
下面执行了Create VM.$el and replace "el" with it
经过这一步后,在模板中所写的{{foo}}
就会被具体的数据替换掉。
所以下面执行mounted
的时候,可以看到真实的数据。同时整个组件内容已经挂载到页面中了,数据以及真实DOM
都已经处理好了,可以在这里操作真实的DOM
了,也就是在mounted
的时候,页面已经被渲染完毕了,在这个钩子函数中,我们可以发生ajax
请求.
mounted() {
console.log("mounted");
console.log("mounted el===", this.$el);
console.log("data==", this.$data);
console.log("foo==", this.foo);
}
所以说,最开始的问题中,D
选项:DOM
渲染在mounted
中就已经完成了 这就话的描述是正确的。
下面继续看生命周期的流程,如下图所示:
当整个组件挂载完成后没有可能会进行数据的修改,当Vue
发现data
中数据发生了变化,会除非对应组件的重新渲染,先后调用了beforUpdate
和updated
钩子函数。
在updated
之前beforUpdate
之后又一个非常重要的操作就是虚拟DOM
会重新构建,也就是新构建的虚拟DOM
与上一次的虚拟DOM
数理由diff
算法进行对比之后重新渲染。
而到了updated
这个方法,就表示数据已经更新完成,dom
也重新render
完成。
下面如果我们调用了 vm.$destroy
方法后,就会销毁所有的资源。
首先会执行beforDestory
这个钩子函数,这个钩子函数在实例销毁前调用,在这一步,实例仍然可用。
在该方法中,可以做一些清理的工作,比如:清除定时器等。
但是执行到destoryed
钩子函数的时候,Vue
实例已经被销毁,所有的事件监听会被移除,所有的子实例也会被销毁。
最后做一个简单的总结:
beforeCreate( )// 该钩子函数执行时,组件实例还未创建.
created()//组件初始化完毕,各种数据可以使用,可以使用ajax发送异步请求获取数据
beforeMounted()// 未执行渲染,更新,虚拟DOM完成,真实DOM未创建
mounted()// 初始化阶段结束,真实DOM已经创建,可以发送异步请求获取数据,也可以访问dom元素
beforeUpdate()//更新前,可用于获取更新前各种状态数据
updated()//更新后执行该钩子函数,所有的状态数据是最新的。
beforeDestroy() // 销毁前执行,可以用于一些定时器的清除。
destroyed()//组件已经销毁,事件监听器被移除,所有的子实例也会被销毁。
以上为生命周期的内容。