最近工作中遇到個bug求豫,半天找不到為什么塌衰,場景大概是這樣的
<template>
<div id="app">
<p>{{data.user_info.nick}}</p>
</div>
<template>
<script>
export default {
name: 'app',
data() {
return {
data:{},
}
},
created() {
this.init();
},
methods:{
init() {
ajax({
url:'hhh',
type:'get',
success() {
this.data = res;
}
})
}
}
}
</script>
運(yùn)行時報(bào)錯
TypeError: Cannot read property ‘nick’ of undefined
[圖片上傳失敗...(image-2d58c6-1541755736318)]
為了快速解決問題,我做了這樣的修改
data() {
return {
data:{
user_info:{}
},
}
},
對象里面有對象蝠嘉,那我就先定義成想要的結(jié)構(gòu)
或者最疆,下面這種解決方法也是可行的
<div id="app">
<p>{{data.user_info.nick}}</p>
</div>
后來,在項(xiàng)目做完了以后蚤告,我嘗試著找到了原因
需要關(guān)注兩點(diǎn)努酸,一是生命周期,二是vue的異步dom更新
下面罩缴,我們來回顧一下vue的生命周期
在我上面的代碼中蚊逢,是把init放到了created階段去執(zhí)行层扶,我們注意created階段的描述,組件實(shí)例創(chuàng)建完成烙荷,屬性已綁定镜会,但是dom還未生成,$el屬性還不存在终抽。所以如果執(zhí)行下面代碼戳表,將不會報(bào)錯
export default {
name: 'app',
data() {
return {
data:{},
}
},
created() {
this.init();
},
methods:{
init() {
this.data = {
user_info:{
nick:'ggg'
}
}
//ajax({
// url:'hhh',
// type:'get',
// success() {
// this.data = res;
// }
//})
}
}
}
因?yàn)樵趇nit中的代碼是同步執(zhí)行的,所以在created階段我們更改了data但是dom并沒有加載昼伴,所以不會報(bào)錯匾旭。
但是,因?yàn)槲野裠ata的改變放在了一個異步請求中圃郊,所以data沒有立即被改變价涝,在它得到數(shù)據(jù)的時候已經(jīng)錯過了上一班車了。(錯過了上一班車的概念等會會解釋)持舆;
然后我們在來看一下如果把init函數(shù)放在mounted中呢色瘩。
mounted階段,模版編譯掛載之后
這個時候dom已經(jīng)生成逸寓,而生成的dom讀取的是原始的data數(shù)據(jù)居兆,所以不管是不是放到請求中去改變data都會報(bào)錯
export default {
name: 'app',
data() {
return {
data:{},
}
},
mounted() {
this.init();
},
methods:{
init() {
this.data = {
user_info:{
nick:'ggg'//TypeError: Cannot read property ‘nick’ of undefined
}
}
}
}
}
export default {
name: 'app',
data() {
return {
data:{},
}
},
mounted() {
this.init();
},
methods:{
init() {
ajax({
url:'hhh',
type:'get',
success() {
this.data = res;//TypeError: Cannot read property ‘nick’ of undefined
}
})
}
}
}
所以其實(shí)其實(shí)我把init寫在created中是不好的,因?yàn)関ue的生命周期的執(zhí)行和請求數(shù)據(jù)是兩個同時在執(zhí)行的線程竹伸,因?yàn)榫W(wǎng)速的快慢泥栖,對于不同用戶,他們可能會在不同的生命周期得到數(shù)據(jù)勋篓。而在created階段因?yàn)閐om還未生成吧享,所以就加大了不確定性。所以最好把請求數(shù)據(jù)寫到mounted中生巡。
然后在來說說剛才的錯過班車的問題
這里我們要來看看vue的內(nèi)部實(shí)現(xiàn)機(jī)制
在new Vue()之后耙蔑,Vue()會調(diào)用_init函數(shù)(不是自己寫的那個init函數(shù))進(jìn)行初始化,它會初始化生命周期(beforeCreated階段)孤荣、事件、props须揣、methods盐股、data、computed與watch等耻卡。其中最重要的是通過Object.defineProperty設(shè)置setter和getter函數(shù)疯汁,用來監(jiān)控data數(shù)據(jù)的改變,實(shí)現(xiàn)所謂的數(shù)據(jù)雙向綁定卵酪。
編譯
complie編譯可以分成parse幌蚊、optimize與generate三個階段
parse
parse負(fù)責(zé)解析template模版中的指令谤碳、class、style等數(shù)據(jù)溢豆,形成AST(抽象語法樹)
optimize
optimize的主要作用是標(biāo)記static靜態(tài)節(jié)點(diǎn)蜒简,這是vue在編譯過程中的一處優(yōu)化,后面update更新界面時漩仙,會有一個patch的過程搓茬,diff算法會直接跳過靜態(tài)節(jié)點(diǎn),從而減少了比較的過程队他,優(yōu)化了patch的性能卷仑。
generate
generate是將AST轉(zhuǎn)化成render function字符串的過程,得到結(jié)果是render的字符串以及staticRenderFns字符串麸折。
在經(jīng)歷過 parse锡凝、optimize 與 generate 這三個階段以后,組件中就會存在渲染 VNode 所需的 render function 了垢啼。
響應(yīng)式
當(dāng)render function被渲染的時候私爷,因?yàn)闀x取所需對象的值,所以會觸發(fā)getter函數(shù)進(jìn)行依賴收集膊夹,依賴收集的目的是將觀察者watcher對象存放到當(dāng)前閉包中的訂閱者Dep的subs中衬浑。在修改對象的值的時候,會觸發(fā)對應(yīng)的 setter放刨, setter 通知之前「依賴收集」得到的 Dep 中的每一個 Watcher工秩,告訴它們自己的值改變了,需要重新渲染視圖进统。
需要注意的是助币,vue并不會在以后對象的修改的時候就更新視圖,它會將這些更新收集起來螟碎,一段時間去更新一次視圖眉菱。
因?yàn)槲覀兤鋵?shí)并不很清楚的知道會更新視圖的具體時間,所以如果有些操作一定要在dom更新之后執(zhí)行掉分,可以使用$nextTick函數(shù)俭缓。
$nextTick
它接收一個回調(diào)函數(shù),回調(diào)函數(shù)會在這次更新完后執(zhí)行酥郭。