说 vue 的生命周期之前我们先来看个 vue 实例
vue 实例
先看一句话: el 属性,template 属性,render 渲染函数 都可以指明 vue 对象对应的 HTML 元素(DOM 对象),三者可以并存,三者的书写方式不同:
- el 属性:对应的 HTML 元素是写在 HTML 文档上的
- template 属性:对应的 HTML 元素直接写该属性后面,作为属性的值
- render 函数:对应的 HTML 元素是写在 createElement 方法里面的,其中写入 data 对象里的变量不能用双括号
{{}},得用this指向 data 对象
另外:
- 只有 template 属性是不行的,因为 vue 对象不知道把 template 放在何处;
- 只有 render 函数也是不行的,因为 vue 对象不知道把 render 后的结果放在何处 只有 el 属性
<body>
<div id="app">
<h1>{{message}} -- 我是本身写在 HTML 里的,el 会指向我</h1>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Vue的生命周期'
}
})
</script>
</body>
由图看出:vue 实例对象里的 el 属性自动指向了 id 为
app 的 <div> 标签
既有 el 属性,又有 template 属性
<body>
<div id="app">
<h1>{{message}} -- 我是本身写在 HTML 里的,el 会指向我</h1>
</div>
<script>
var vm = new Vue({
el: '#app',
template: "<h1>{{message}} -- 我是 template 属性出来的 HTML</h1>", // 在vue配置项中修改的
data: {
message: 'Vue的生命周期'
}
})
</script>
</body>
由图看出:vue 实例对象里的 template 属性里的 HTML 元素代替了 el 属性指向的
<div> 标签里的元素
既有 el 属性和 template 属性,又有 render (渲染)函数
<body>
<div id="app">
<h1>{{message}} -- 我是本身写在 HTML 里的,el 会指向我</h1>
</div>
<script>
var vm = new Vue({
el: '#app',
template: "<h1>{{message}} -- 我是 template 属性出来的 HTML</h1>", // 在vue配置项中修改的
data: {
message: 'Vue的生命周期'
},
render:function(createElement){
// return createElement('h1', '{{message}} -- 我是 render 函数出来的 HTML');
// 以上写法是错误的,因为使用 JavaScript 不能使用 “Mustache” 语法 (双大括号)
return createElement('h1', this.message + ' -- 我是 render 函数出来的 HTML');
}
})
</script>
</body>
由图看出:vue 实例对象里的 render 函数里写的 HTML 元素代替了 template 属性写的 HTML 元素
由此可以看出:
- el 属性,template 属性,render 渲染函数 都可以用来指明 vue 对象对应的 HTML 元素
- 而前两种方式引用 data 对象里面的变量时可以使用
{{}},render 函数就得用this指向 data 对象然后才能使用 data 对象里面的变量 - 并且优先级顺序为:el < template < render
其中 el 属性是 必须的,因为它的作用就是:用来 指定 vue 要管理的 DOM 元素,可以帮助解析其中的指令、事件监听等等
生命周期图示
参考网上对官网的图示解读,让我们对 vue 的生命周期有个大概的了解:
从这张图我们可以看出:一个 vue 实例中有 8 个状态,对应 8 个钩子函数,每两个钩子函数之间对应一个生命周期,即 7 个生命周期,下面我们来一步一步拆解这些周期:
创建和挂载阶段
首先我们需要知道在 vue 这几个生命周期都大概干了什么,在每个生命周期结束后对应的状态,或者说是钩子函数能够获取到什么数据,或者说我们可以做哪些工作
借用 详解vue生命周期 里的代码,我们在控制台输出各个状态里的 el 属性、data 对象以及 data 里的 message 变量:
创建阶段
对应于官网中生命周期的图示,我们可以知道:从 beforeCreate 到 created 之间的生命周期中,vue 进行初始化事件,进行数据的观测,也就是说在 created 钩子函数里面可以拿到 data 的值了,并且进行绑定(放在 data 中的属性当值发生改变的同时,视图也会改变)。
注意:el 属性的值还是 undefined
挂载阶段
上面说 el 属性是必须的,因为它是负责挂载工作,那如果没有 el 属性会怎样
可以看到:如果没有 el 属性,vue 实例就会处于等待状态,此时虽然拿到了 data 对象,但是并没能够把,由于 el 属性对应的 HTML 元素是写在 HTML 文档里的,所以能够渲染出{{message}}出来,
所以没有这个属性 vue 会怎么处理?官网的图告诉我们答案:
vue 会调用vm.$mount(el),我们可以测试一下,运行vm.$mount("#app")来代替 el: "#app":
代码继续往下编译,成功完成预挂载和挂载,因此我们可以得出结论:
从 created 到 beforeMount 之间的生命周期中,el 属性的值不再是 undefined,而是变成了<div id="app"><h1>{{message}}</h1></div>,也就是说,在这个生命周期里面,vue 完成了把 el 属性挂载到 HTML 元素的操作,定位方式就是根据 HTML 标签里的 id 和 el 属性的值(有点像选择器)
另外,vue 实例还会判断是否还有 template 属性,就是我们在最上面说的那个,接下来进入判断:
- 如果有
template属性 vue 实例就会把template属性的值 HTML 元素写到render函数里面 - 否则就把 el 属性所指向的 HTML 元素写到
render函数里面;
经历了这些操作,vue 实例才能完成预挂载,这就对应上最上面说的【优先级顺序】了
接下来看 从 beforeMount 到 mounted 之间的生命周期 中完成了什么样的工作:
从这张图可以看出:HTML 元素还是那个 HTML 元素,区别就在于 {{message}} 被换成了 Vue的生命周期( data 对象 message 属性的值)
也就是说:在 mounted 之前 h1 中还是通过 {{message}} 进行占位的,因为此时是以 JavaScript 中的 虚拟 DOM 形式 挂载在页面上的。在 mounted 之后用真实 DOM 把虚拟 DOM 替换掉
数据更新阶段
从 mounted 到 beforeUpdate 之间的生命周期:更新了 data 值,但 view 层中对应的 DOM 元素里的数据和 data 还没同步
从 beforeUpdate 到 updated 之间的生命周期:页面重新渲染,view 层中对应的 DOM 元素里的数据和 data 保持一致
<body>
<div id="app">
<p>{{message}}</p>
</div>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
message: "hello world!"
},
beforeUpdate () {
console.log("此时的message已经更新,但是浏览器页面上的数据没有发生变化",this.message)
debugger // 修改数据之后,添加断点
},
updated () {
console.log("更新结束,页面数据已更新")
}
})
debugger // 在修改数据之前,添加断点
vm.message = '你好,Vue生命周期的运行阶段'
</script>
</body>
修改数据之后,添加断点,页面上的数据和当前 message 不一致:
值得注意的是:如果当前页面有挂载子组件,子组件更新时它并不能保证全部的子组件也重绘了。如果你想确定是否整个 DOM 都更新,可以使用
vm.$nextTick()来代替 mounted 钩子函数
会得到一个 Promise 对象,对该对象使用 then、catch、await 等方法都可以获知是否整个视图都重新渲染完毕
另外,vm.$nextTick() 方法也可以用来区分不同的数据更新,针对每一个变化做不同的处理
销毁阶段
从 updated 到 beforeDestroy 之间的生命周期:进入销毁阶段,到了 beforeDestroy 状态:虽然 Vue的生命周期进入了销毁阶段,但是实例上的各种数据还出于可用状态
从 beforeDestroy 到 destroyed 之间的生命周期:完成数据销毁,到了 destroyed 状态:组件已经全部销毁,Vue实例已经被销毁,Vue中的任何数据都不可用
总结(7 个生命周期干了什么)
- 从 beforeCreate 到 created 之间的生命周期:
初始化 data 对象和 Events 事件 - 从 created 到 beforeMount 之间的生命周期:完成 HTML 元素(DOM 元素) 的预挂载,el 属性不再是 undefined 了,其值被
虚拟 DOM 替换掉 - 从 beforeMount 到 mounted 之间的生命周期:完成 HTML 元素(DOM 元素) 的挂载,把 虚拟 DOM 替换成
真实 DOM(简单来说就是把{{}}里面的变量换成它在 data 对象里面对应的值) - 从 mounted 到 beforeUpdate 之间的生命周期:
监听到数据变化,更新 data 值,但 view 层数据未更新 - 从 beforeUpdate 到 updated 之间的生命周期:
重新渲染页面,使得 view 层中对应DOM 元素的数据和 data 值保持一致 - 从 updated 到 beforeDestroy 之间的生命周期:进入销毁阶段,但数据仍然可以用
- 从 beforeDestroy 到 destroyed 之间的生命周期:完成数据销毁
各个状态(钩子函数)可以干什么事
- created 钩子函数:可以
获取 data 值,并且可以进行异步 AJAX 请求 - mounted 钩子函数:可以
获取真实的 DOM 元素,当需要操作 dom 的时候执行,可以配合$.nextTick 使用进行单一事件对数据的更新后更新 dom - beforeUpdate 钩子函数:可以用 watch
监听数据变化,因为还没有挂载到页面上,因此我们可以做更新数据前的最后修改 - updated 钩子函数:可以获取到
更新后的 DOM 元素
注意事项
所有的钩子函数里面的 this 全部指向 vue 实例。所以所有的钩子函数后面都必须跟普通函数而不是箭头函数,因为箭头函数里面没有 this,所以会向上级作用域查找,因此 this 指向可能变成 window 之类其他的对象,导致会报一些 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function之类的错误。