36.Vue引出生命周期_未来@音律的博客-CSDN博客下面绿色框中的是Vue实例,红色框中的是具体的逻辑,而且最主要的是红色框中的还在操作绿色框中的数据。这是因为在定时器里,我们修改了data中的数据opacity,而Vue有这样的承诺,只要修改了data中的数据,那它就会帮我们重新解析模板,而一重新解析模板,那么插值语法中的change就又会调用一次,所以才会有这种无限循环创建定时器的情况发生。所以我们可以发现,所谓的生命周期,其实就是指Vue中特殊的函数,像上面我们使用到的mounted函数,就是Vue在一个关键性的时刻帮我们调用的。https://blog.csdn.net/qq_37050372/article/details/131813466?spm=1001.2014.3001.5501在上一节,我们给大家引出了mounted这个生命周期函数,并且说了一下它是什么时候调用的。
实际上,Vue在调用mounted之前和之后,Vue还会帮我们调用几个别的函数。也就是说mounted并不是在单打独斗,还有他的其他兄弟在陪着他,那么这一节,我们就来说一说他的这些兄弟们都是什么时候被调用的。
如果我们想研究最完整的生命周期,那我们最好参考最完整的一个生命周期图,如下:
有的同学在看到这张图的时候就紧张了,就感觉这个图好长,不好记啊。但我们分析完了之后,就会发现,很多的环节,我们了解知道就可以了。真正我们使用的钩子就那几个。
接下来我们就会照着图一步一步的分析整个流程。这一节,我们只分析挂载的流程。
我们注意到,Vue首先进行的是init Events & Lifecycle, 初始化事件和生命周期。这个环节它干嘛了呢?他去制定了一些规则,比如说生命周期函数到底有几个?都叫什么名字?什么时候去调用这些函数?都需要在这里去制定。还有关于事件,比如事件修饰符once,以后遇见这种修饰符该怎样处理,也都需要在这里去制定。
这里还有一句话,但数据代理还未开始, 这句话很重要,这也就意味着,我们在data中定义的数据,vm还没有收到,vm身上还不存在_data。
有的同学此时就会觉得,好了,讲到这里,第一个生命周期就讲完了,但其实这并不算一个生命周期,顶多算一个环节。图中红框标出来的beforeCreate,created那些才是生命周期。而且这些环节,我们也仅仅是知道一下,并不能对其进行干预。
这个环节结束之后,Vue马上帮我们调用了一个函数beforeCreate,这才是第一个生命周期函数。我们可以从图中看到,在这个函数中,我们无法通过vm访问到data中的数据,methods中的方法。这是因为此时的vm身上还不存在_data,接下来我们就验证一下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
console.log(this);
debugger
},
})
</script>
</html>
执行效果:
我们可以看到此时的vm身上没有_data,也没有vm.n,,而且也没有我们定义的add方法,这就强有力的证明了,数据代理还没开始。
顺着图再往下走,我们可以看到 Init injections & reactivity , 初始化注入响应式?翻译成大家能理解的话就是,它在这个阶段也在初始化,初始化什么呢?最重要的东西就是数据监测和数据代理。
也就是说,咱们之前分析的Vue是如何监测对象变化的?如何监测数组变化的?给对象中的属性匹配getter,setter,然后对操作数组的一些方法进行二次包装,数据监测都是在这里完成的,还有咱们之前分析的数据代理也都是在这里完成的。它一旦完成了这两个东西,就开始执行created。那也就意味着此时可以访问到data中的数据还有methods中配置的方法。
我们可以输出验证一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
console.log(this);
debugger
},
})
</script>
</html>
输出效果:
此时我们可以看到vm身上不仅有了add方法,还有data中的n,而且还能看到_data。
接下来,它问了一个问题,有没有在new Vue的时候有没有传递el配置项,我们这里传了root,那么顺着往下走,他又问我们有没有传递template配置项。
这里有人会想起我们之前用过的<template></template>,但这个其实是个标签。并不是配置项,这里它问的是在new Vue的时候,有没有传入一个名字叫template的配置项。我们这里显然没有,没有的话,那么显然就走了NO的那一条线。
那么接下来重点就来了,Compile el‘s outerHTML as template, 编译el的外部html作为模板。这里其实很细节。我们看下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
console.log(this);
debugger
},
})
</script>
</html>
我们代码里写了一个el:'#root',这个指的是下方红色框中的内容。
那么el的outerHTML怎么理解呢?其实这个问题就相当于在问,红色框的是模板还是绿色框的是模板。
我们可以验证一下,我们在root的div上加一个:x="n",如果红色框不算模板,那:x="n"是不会被解析的,最终也会原模原样输出。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
console.log(this);
},
})
</script>
</html>
所以整个root的这个div都算做模板。所以这里的outerHTML的意思就是,连那个div标签都算做模板。
到这里,一个最重要的环节又出现了,此阶段Vue开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容。
页面还不能显示解析好的内容是因为这个东西还没有变成真实DOM,还没有放到页面上。
我们可以把断点卡在beforeMount这里,去验证一下页面是不是呈现的未经编译的DOM结构。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
console.log(this);
debugger
},
})
</script>
</html>
执行效果:
接着再往下走,create vm $el and replace 'el' with it, 创建一个vm身上的$el选项,然后替换掉整个el。这里有人就会问$el是什么?
在上一步,Vue把刚才生成的虚拟DOM转成了真实DOM,转成真实DOM之后,它往vm身上的$el上存了一份。说白了,Vue在将虚拟DOM转成真实DOM之后,并不是傻乎乎的直接放到页面上去,而是自己偷偷的存了一份在vm身上的$el
随后就进入到了mounted的步骤,也就是挂载完毕。
这里我们还遗漏了beforeMount中的一个知识点,在beforeMount中所有对DOM的操作最终都不奏效。我们下面可以测试一下这个点。
我们把断点放在beforeMount上,然后修改完DOM之后放行,看最终的DOM有没有被修改即可验证。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
console.log(this);
debugger
},
})
</script>
</html>
通过上面的操作我们可以看到在beforeMount中针对DOM的操作最终都是不生效的。所以就不要在beforeMount中去操作DOM了,即使操作了也是白操作。
我们再接着往下说,一旦Vue把真实DOM插入页面,他就会帮我们调用mounted,mounted就是挂载完毕。
这个时候页面中呈现的就是经过Vue编译的DOM,此时对DOM的操作都是有效的,但是尽可能的要避免,因为我们使用Vue就是为了避免自己操作DOM,至此初始化过程结束。
上面讲的是一个顺利的流程,这个图中还有几条线没有讲到,我们这里也讲一下。
如果这里没有el配置,且没有显式的调用mount那么会发生什么?我们下面可以写个例子看看。
首先看正常执行的时候会输出什么
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
})
</script>
</html>
执行结果:(输出了4个生命周期的步骤)
如果没有配置el,会输出什么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
// el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
})
</script>
</html>
执行结果:(只输出了两个,流程走到created就停了)
如果我们显式的调用vm.mount呢
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
// el:'#root',
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
})
</script>
</html>
我们可以看到显式的调用$mount之后,流程也可以正常运行。
还有另外一条线没说:
这里问我们是否有模板配置。 如果有template配置,则会将template作为模板解析并转为真实DOM放入页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
</div>
</body>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
template: `<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>`,
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
})
</script>
</html>
执行结果:
可以看到这里显示编译错误, 这是因为如果我们使用了template配置项,那就意味着不能有两个根节点,我们可以在外面包一层div。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
template: `
<div>
<h2 >当前的n值是: {{n}}</h2>
<button @click="add"> 点我n+1 </button>
</div>`,
data:{
n:1
},
methods:{
add(){
this.n++;
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
})
</script>
</html>
执行效果:
到这里我们就把整个挂载流程就都说完了。