Vue框架已日臻成熟行瑞,生命周期也算是老生常談了。網(wǎng)路上也有很多對Vue生命周期的講解餐禁。
此處是補充上自己的理解血久,再次總結(jié)一下。
一帮非、什么是生命周期(LifeCycle)氧吐?
生命周期在計算機語言里绷旗,生命周期一般是指一個對象的創(chuàng)建(生)到銷毀(死)的階段。
二副砍、Vue的生命周期
2.1 生命周期圖解
對于Vue的生命周期衔肢,就是其組件的生命周期。具體可以看下圖(相對官方文檔的圖豁翎,已補充翻譯):
從圖中可以直觀注意到,Vue的生命周期可以劃分為四個階段:
-
create
階段: vue實例被創(chuàng)建心剥; -
mount
階段: vue實例被掛載到真是的DOM節(jié)點邦尊; -
update
階段:當vue實例里面的data數(shù)據(jù)變化時,觸發(fā)組件的重新渲染优烧; -
destroy
階段:vue實例被銷毀蝉揍。
2.2 階段分解
上面的四個階段,每個階段分為開始前和開始后畦娄,這樣就衍生出了9個方法:
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
-
destroyed
——也就是所謂的鉤子函數(shù)又沾。
接下來我們一個階段一個階段來分析它們的觸發(fā)條件及表征。
2.2.0 實驗代碼準備
(1)一個最簡單的Vue實例化代碼如下:
<div id="app">
<p>{{ message }}</p>
</div>
var vm = new Vue({
el: '#app',
data: {message: 'Hello Vue~'},
});
我們主要關(guān)心著這個實例化里的屬性:元素el
什么時候有值(掛載上了熙卡?)杖刷、data
什么時候有數(shù)據(jù)?驳癌、message
又是什么時候有數(shù)據(jù)呢滑燃?
于是,我們在每個回調(diào)里面去打印下這三個值看看颓鲜。
(2)為了更直觀去理解Vue實例的變化表窘,我們引用了一段實驗代碼(該源碼來源于參考文章1):
<div id="app">
<p>{{ message }}</p>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {message: 'Hello Vue~'},
beforeCreate: function(){
console.group('beforeCreate 創(chuàng)建前 =========')
console.log('%c%s','color:red','el :',this.$el) // 1 => undefined
console.log('%c%s','color:red','data :',this.$data) // 1 => undefined
console.log('%c%s','color:red','message :',this.message) // 1 => undefined
},
created: function(){
console.group('created 創(chuàng)建完畢 =========')
console.log('%c%s','color:red','el :',this.$el) // 1 => undefined
console.log('%c%s','color:red','data :',this.$data) // 1 => {message: 'Hello Vue~'}
console.log('%c%s','color:red','message :',this.message) // 1 => 'Hello Vue~'
},
beforeMount: function(){
console.group('beforeMount 掛載前 =========')
console.log('%c%s','color:red','el :',this.$el) // 1 => <div id="app"><p>{{ message }}</p></div>
console.log('%c%s','color:red','data :',this.$data) // 1 => {message: 'Hello Vue~'}
console.log('%c%s','color:red','message :',this.message) // 1 => 'Hello Vue~'
},
mounted: function(){
console.group('mounted 掛載結(jié)束 =========')
console.log('%c%s','color:red','el :',this.$el) // 1 => <div id="app"><p>'Hello Vue~'</p></div>
console.log('%c%s','color:red','data :',this.$data) // 1 => {message: 'Hello Vue~'}
console.log('%c%s','color:red','message :',this.message) // 1 => 'Hello Vue~'
},
beforeUpdate: function(){
console.group('beforeUpdate 更新前 =========')
console.log('%c%s','color:red','el :',this.$el) // 2 => <div id="app"><p>'Celine~'</p></div>
console.log('%c%s','color:red','data :',this.$data) // 2 => {message: 'Celine~'}
console.log('%c%s','color:red','message :',this.message) // 2 => 'Celine~'
},
updated: function() {
console.group('updated 更新完成 =========')
console.log('%c%s','color:red','el :',this.$el) // 2 => <div id="app"><p>'Celine~'</p></div>
console.log('%c%s','color:red','data :',this.$data) // 2 => {message: 'Celine~'}
console.log('%c%s','color:red','message :',this.message) // 2 => 'Celine~
},
beforeDestroy: function(){
console.group('beforeDestroy 銷毀前 =========')
console.log('%c%s','color:red','el :',this.$el) // 3 => <div id="app"><p>'Celine~'</p></div>
console.log('%c%s','color:red','data :',this.$data) // 3 => {message: 'Celine~'}
console.log('%c%s','color:red','message :',this.message) // 3 => 'Celine~
},
destroyed: function(){
console.group('destroyed 銷毀完成 =========')
console.log('%c%s','color:red','el :',this.$el) // 3 => <div id="app"><p>'Celine~'</p></div>
console.log('%c%s','color:red','data :',this.$data) // 3 => {message: 'Celine~'}
console.log('%c%s','color:red','message :',this.message) // 3 => 'Celine~
}
})
// 1 頁面直接刷新進來,執(zhí)行的鉤子有 beforeCreate / created / beforeMount / mounted 四個甜滨。
// 2 在控制臺執(zhí)行 vm.message = "Celine~" 后乐严,執(zhí)行的鉤子有 beforeUpdate / updated 兩個。
// 更新前和更新后的艳吠,打印數(shù)據(jù)均是新數(shù)據(jù) 麦备?? 這點和想象的不太一致昭娩。
// 3 在控制臺執(zhí)行 vm.$destroy() 后,執(zhí)行的鉤子有 beforeDestroy / destroyed 兩個黍匾。
// 銷毀前和銷毀后栏渺,數(shù)據(jù)依舊存在?锐涯? 這點也和想象的不太一致磕诊。不過這個時候再去改變message值,vue不會再響應(yīng)(也不會去執(zhí)行beforeUpdate / updated 鉤子)
// 4 在控制臺執(zhí)行 vm.message = "Bye~" ,沒有任何鉤子有響應(yīng)霎终。
</script>
返回結(jié)果:
由此可見滞磺,當代碼運行時,會一次調(diào)用Vue 的 beforeCreate莱褒、created击困、beforeMount、mounted 四個方法广凸,直至完成組件的掛載阅茶。
而update階段,要在數(shù)據(jù)發(fā)生改變時(比如更新message字段 vm.message = 'Hahahaha~'
)才出發(fā)谅海;destroy階段脸哀,要在調(diào)用vm.$destroy()
后才觸發(fā)。
下面我們來分別分析每個階段:
2.2.1 create階段
從生命周期圖可以看出扭吁,在這個階段主要做兩件事:
- 監(jiān)控Data數(shù)據(jù)
- 初始化內(nèi)部事件
從控制臺打印數(shù)據(jù)撞蜂,可以看出:在beforeCreate
時,因為啥動作都還沒有開始侥袜,所以vm.$el
,vm.$data
,vm.message
都是undefined
谅摄。
而在created
時,因為已開始監(jiān)控Data數(shù)據(jù)系馆,所以data
,message
都有值送漠。至于初始化內(nèi)部事件是什么,我們此處暫不表由蘑,后續(xù)補充闽寡。
2.2.2 mount階段
在這個階段,做的事情就比較多了尼酿。
掛載前:
- 先判斷是否有
el
選項(我們的上方示例代碼中有該選項爷狈,即el: '#app',
); - 如果有
el
選項裳擎,接著判斷是否有template
選項(此處我們的示例代碼中并沒有該選項)涎永; - 如果有
template
選項,編譯該模板并導(dǎo)入到渲染函數(shù)鹿响;如果沒有template
選項羡微,就把實例模板el
指向的DOM的outerHTML(上方示例代碼即為<div id="app"><p>{{ message }}</p></div>
)作為模板.
掛載后:
- 創(chuàng)建
vm.$el
,并用上面編譯好的模板(html內(nèi)容)替換el指向的DOM惶我。
從控制臺打印數(shù)據(jù)就可以看出:
掛載前妈倔,vm.$el
為<div id="app"><p>{{ message }}</p></div>
,看起來似乎有值了绸贡,但又不太對勁——因為message
的值沒有代入進入盯蝴。其實此處有點像虛擬DOM的效果:也就是我的vm.$el
雖然不是完整的毅哗,但也先準備著。(所以捧挺,在其他版本的瀏覽器中虑绵,此處也可能是打印出undefined
。)
掛載結(jié)束就不一樣了闽烙,三個數(shù)據(jù)項都就緒了翅睛。vm.$el
也成為了完美的<div id="app"><p>Hello Vue~</p></div>
。
以上的結(jié)果是按照有el
選項鸣峭,沒有template
選項的情況執(zhí)行的宏所。接下來我們看看其他種情況下,會發(fā)生點什么摊溶?
(1)沒有el
選項
操作:注釋掉 el: "#app"
結(jié)果:只進行了create階段爬骤,沒有進行mount階段。
原因分析:這個好理解莫换,因為沒有了el
選項霞玄,就無從掛載起了。生命周期也就結(jié)束了拉岁。
如果此時想進行掛載坷剧,可以手動去調(diào)用vm.$mount(el)
。
(2)手動掛載
操作:在new Vue({...})
后面執(zhí)行 vm.$mount('#app')
結(jié)果:進行了create階段后喊暖,也進行了mount階段惫企。
(3)有template
選項時
之前的實例代碼是沒有template
選項的情況表現(xiàn),此處我們看下若有template
選項陵叽,會發(fā)生點什么呢狞尔?
var vm = new Vue({
el: '#app',
data: {message: 'Hello Vue~'},
template: "<h1>我是模板標題1</h1>",
}
操作: 在new Vue({...})
里新增 template
選項,如上巩掺。
結(jié)果: vm.$el
變成了template
選項的內(nèi)容偏序;DOM節(jié)點#app
也替換成template
選項的內(nèi)容了。其實掛載后胖替,vm.$el
是什么研儒,DOM節(jié)點#app
也對應(yīng)是什么,它們是等價的独令。
分析: 此處驗證了前面生命周期圖:若有template
選項端朵,就編譯它并導(dǎo)入到渲染函數(shù);若沒有template
選項记焊,就取#app
的outerHTML作為模板逸月。
(4)render()
方法
Vue實例里還有render()
方法可以提供模板,我們看下如果存在render()
方法遍膜,會發(fā)生什么碗硬?
var vm = new Vue({
el: '#app',
data: {message: 'Hello Vue~'},
template: "<h1>我是模板標題1</h1>",
render(h){
return h('h2','這是render出來的標題2');
},
}
操作: 在new Vue({...})
里新增 render(h)
方法,如上瓢颅。
結(jié)果: vm.$el
變成了render
返回的模板內(nèi)容恩尾。
分析: 也就是說,渲染模板的優(yōu)先級可以小結(jié)為:render()
方法 > template
選項 > el
屬性的outerHTML挽懦。
以上就是掛載階段的一序列可能性變化翰意。接下來我們看下更新階段。
2.2.3 update階段
更新階段的前提是:“when data changes”也就是說當
data
選項里的數(shù)據(jù)有變化時觸發(fā)信柿。讓數(shù)據(jù)改變有很多操作方式冀偶,此處我們簡單的在控制臺對
message
字段進行改寫。
// 在控制臺輸入:
vm.message = "Now update!!" //直接回車
當進行了數(shù)據(jù)更新渔嚷,就會觸發(fā)
beforeUpdate
方法和 updated
方法进鸠。此處在控制臺打印出來的數(shù)據(jù)和預(yù)想的有出入:
beforeUpdate
本應(yīng)該輸出的舊數(shù)據(jù)(message: Hello Vue~
),但此處更新前后的數(shù)據(jù)卻顯示一樣形病。
在參考文章2里面客年,說這是打印出來的是虛擬DOM,都已更新漠吻,但真實DOM還沒有改變量瓜。但我個人覺得不一定是這樣。我嘗試過在各個鉤子函數(shù)補充打印出DOM元素(如下代碼)途乃,但結(jié)果更新前后也都是更新后的數(shù)據(jù)绍傲。
console.log('%c%s','color:red','#app DOM :',document.getElementById("app"))
我個人更傾向于是因為控制臺本身原因。在beforeUpdate
時可能確實是舊數(shù)據(jù)耍共,只不過往下執(zhí)行updated
時候烫饼,更新新數(shù)據(jù)時,也改寫了beforeUpdate
部分的數(shù)據(jù)划提。(待進一步探討研究補充枫弟。)
2.2.4 destroy階段
銷毀階段,需要執(zhí)行
vm.$destroy()
才會進入鹏往。同樣的淡诗,我們在控制臺執(zhí)行銷毀方法,得到如下結(jié)果:
可以看出伊履,銷毀前后的數(shù)據(jù)是一樣的韩容,但實際上,銷毀后Vue實例會接觸所有綁定唐瀑,所有事件被移除群凶,子組件被銷毀。比如我們此處更新
data
數(shù)據(jù)項 vm.message
哄辣,可以發(fā)現(xiàn)请梢,不會在觸發(fā)update階段了赠尾。2.3 生命周期小結(jié)
我們對上面的分析結(jié)果做個小結(jié),此處的表格會多考慮兩個方法(當有<keep-alive></keep-alive>
組件時毅弧,生命周期會多出現(xiàn)一個activate
階段)气嫁。
方法名 | 特征 | 屬性變化 |
---|---|---|
beforeCreate | 組件實例創(chuàng)建前(或者說剛被創(chuàng)建),啥也沒有够坐。 | $el寸宵、data 的值都為undefined。 |
created | 組件實例創(chuàng)建完成元咙。屬性已綁定梯影,但DOM還未產(chǎn)生。 | data有值了庶香,$el屬性還是undefined甲棍。 |
beforeMount | 模板編譯/掛載前。 | $el是虛擬DOM脉课。 |
mounted | 模板編譯/掛載后救军。 | “虛擬”的dom節(jié)點被真實的dom節(jié)點替換,并將其插入到dom樹中倘零。此時可以獲取到$el為真實的dom元素唱遭。 |
beforeUpdate | 組件更新之前。 | $el呈驶、data 的值都為新數(shù)據(jù)拷泽。 |
updated | 組件更新之后。 | $el袖瞻、data 的值都為新數(shù)據(jù)司致。 |
activated | for kepp-alive ,組件被激活時調(diào)用聋迎。 |
(待補充) |
deactivated | for kepp-alive 脂矫,組件被移除時調(diào)用。 |
(待補充) |
beforeDestroy | 組件銷毀前嗲用霉晕。此時實例仍可用庭再。 | $el、data 都有值牺堰。實例綁定的事件還存在拄轻。 |
destroyed | 組件銷毀后調(diào)用。 | $el伟葫、data 雖然都有值恨搓。但實例綁定的事件和子組件都沒有了。 |
3 了解生命周期的作用
我們?nèi)リP(guān)注聲明周期,是為了能更好的判斷在不同的生命周期鉤子函數(shù)里面做些什么操作和處理斧抱。比如:
-
beforeCreate
- 加入loading事件 -
created
- 結(jié)束loading -
beforemount
- 發(fā)起服務(wù)端請求常拓,取數(shù)據(jù) -
mounted
- 根據(jù)請求數(shù)據(jù),對頁面DOM做些什么操作
……
具體每個階段的做些什么夺姑,還是要根據(jù)實際場景來設(shè)定咯~
-----------------------HAPPY END--------------------------------
參考文獻:
- segmentFaul: Vue2.0 探索之路——生命周期和鉤子函數(shù)的一些理解 本文的主實例代碼來源
- 簡書:05墩邀、手把手教Vue--生命周期 本文掛載階段的分類案例來源
- cnblogs:Vue生命周期 聲明周期分析方法最初來源