computed 和 watch 在 Vue 項(xiàng)目中可以說(shuō)是非常常見(jiàn),這兩個(gè)方法看似都能實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)聽(tīng),那么兩者之間有什么區(qū)別呢?
computed 計(jì)算屬性
計(jì)算屬性基于 data 中聲明過(guò)或者父組件傳遞的 props 中的數(shù)據(jù)通過(guò)計(jì)算得到的一個(gè)新值,這個(gè)新值只會(huì)根據(jù)已知值的變化而變化。通俗來(lái)講就是:這個(gè)屬性依賴其他屬性,由其他屬性計(jì)算而來(lái)。 得出使用 computed 的常用場(chǎng)景為:
如果一個(gè)屬性是由其他屬性計(jì)算而來(lái)的,這個(gè)屬性依賴其他屬性,是一個(gè)多對(duì)一或者一對(duì)一,一般用
computed。
先來(lái)看一看 computed 最常用的場(chǎng)景栗子:
<p>姓名:{{ fullName }}</p>
<script>
export default {
data() {
return {
firstName: '張',
lastName: '三'
}
},
computed: {
fullName() {
return this.firstName + this.lastName
}
}
};
</script>
在 computed 屬性對(duì)象中定義計(jì)算屬性的方法,和取 data 對(duì)象里的數(shù)據(jù)屬性一樣以屬性訪問(wèn)的形式調(diào)用,即在頁(yè)面中使用 {{ 方法名 }} 來(lái)顯示計(jì)算的結(jié)果。
如上栗子中我們?cè)?computed 中使用了 fullName 方法,如果此時(shí)我們?cè)?data 中也聲明一個(gè)屬性 fullName,會(huì)報(bào)錯(cuò) Duplicated key 'fullName'。因?yàn)?data 中的 fullName 屬性和 computed 中的 fullName 方法存在重復(fù)。如下栗子:
// 錯(cuò)誤用法
data() {
return {
firstName: '張',
lastName: '三',
fullName: '張三'
}
},
computed: {
fullName() {
return this.firstName + this.lastName
}
}
在 Vue 的官方文檔中,還特意強(qiáng)調(diào)了 computed 的一個(gè)重要特點(diǎn),就是它具有緩存功能,我們先來(lái)看個(gè)栗子:
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<script>
export default {
data() {
return {
firstName: '張',
lastName: '三'
}
},
computed: {
fullName() {
console.log('computed') // 控制臺(tái)只打印了一次
return this.firstName + this.lastName
}
}
};
</script>
我們?cè)陧?yè)面上多次顯示 fullName ,實(shí)際上這個(gè)方法只執(zhí)行了一次,所以此處又可得出結(jié)論:computed 中的內(nèi)部方法在重復(fù)的調(diào)用中,只要依賴數(shù)據(jù)不變,直接取緩存中的計(jì)算結(jié)果。只有依賴型數(shù)據(jù)發(fā)生改變,computed 才會(huì)重新計(jì)算。
在 computed 中,所有的屬性都有一個(gè) get() 和一個(gè) set(),默認(rèn)一般走 get() ,但是如果我們直接修改 fullName 時(shí),則會(huì)調(diào)用 set() 方法。我們將上述代碼使用 get() 和 set() 進(jìn)行拆分,得到如下栗子:
<p>姓名:{{ fullName }}</p>
<button @click="handleClick">更改姓名</button>
<script>
export default {
data() {
return {
firstName: '張',
lastName: '三'
}
},
computed: {
fullName: {
get() {
return this.firstName + this.lastName
},
set(val) {
console.log(val) // 李四
}
}
}
methods: {
handleClick() {
this.fullName = '李四'
}
}
};
</script>
同時(shí) computed 中不支持異步,當(dāng)computed內(nèi)有異步操作時(shí)會(huì)報(bào)錯(cuò),這里用 setTimeout 演示栗子如下:
computed: {
fullName() {
setTimeout(() => { // 報(bào)錯(cuò)
return this.firstName + this.lastName
}, 30)
}
}
所以綜上總結(jié) computed 的一些特點(diǎn)如下:
- 支持緩存,只有依賴數(shù)據(jù)發(fā)生改變,才會(huì)重新進(jìn)行計(jì)算
- 不支持異步,當(dāng)
computed內(nèi)有異步操作時(shí)無(wú)效 - 如果一個(gè)屬性是由其他屬性計(jì)算而來(lái)的,這個(gè)屬性依賴其他屬性,是一個(gè)多對(duì)一或者一對(duì)一,一般用
computed - 如果
computed屬性屬性值是函數(shù),那么默認(rèn)會(huì)走get();函數(shù)的返回值就是屬性的屬性值;在computed中的,屬性都有一個(gè)get()和一個(gè)set(),當(dāng)數(shù)據(jù)變化時(shí),調(diào)用set()。
watch 監(jiān)聽(tīng)屬性
通過(guò) vm 對(duì)象的 $watch() 或 watch 配置來(lái)監(jiān)聽(tīng) Vue 實(shí)例上的屬性變化,或某些特定數(shù)據(jù)的變化,然后執(zhí)行某些具體的業(yè)務(wù)邏輯和操作。當(dāng)屬性變化時(shí),回調(diào)函數(shù)自動(dòng)調(diào)用,在函數(shù)內(nèi)部進(jìn)行計(jì)算。其可以監(jiān)聽(tīng)的數(shù)據(jù)來(lái)源:data,props,computed 內(nèi)的數(shù)據(jù)。
監(jiān)聽(tīng)函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)是最新的值,第二個(gè)參數(shù)是輸入之前的值,順序一定是先新值再舊值,如果只寫一個(gè)參數(shù),那就是最新屬性值。
在使用時(shí)選擇 watch 還是 computed,還有一個(gè)參考點(diǎn)就是官網(wǎng)說(shuō)的:當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開(kāi)銷較大的操作時(shí),watch方式是最有用的。所以 watch 一定是支持異步的。
watch 默認(rèn)的是淺監(jiān)聽(tīng),(淺監(jiān)聽(tīng)指只能監(jiān)聽(tīng)到基礎(chǔ)類型的值變化情況),無(wú)法進(jìn)行深度監(jiān)聽(tīng)(深度監(jiān)聽(tīng)指比如引用類型的值變化就無(wú)法正確監(jiān)聽(tīng))。直接看個(gè)栗子吧:
<div>
<input type="text" v-model="name" />
<input type="text" v-model="info.city" />
<button @click="handleClickName">更改姓名</button>
<button @click="handleClickInfo">更改信息</button>
</div>
<script>
export default {
data() {
return {
name: '哪吒',
info: {
city: '北京'
}
}
},
watch: {
name(newVal, oldVal) {
// 值類型,可正常拿到
console.log('watch name', newVal, oldVal) // 姜子牙,哪吒
},
info(newVal, oldVal) {
// 引用類型,拿不到
console.log('watch info', newVal, oldVal) // 控制臺(tái)無(wú)輸出結(jié)果
},
info: {
handler(newVal, oldVal) {
console.log('watch info', newVal, oldVal)
},
deep: true // 深度監(jiān)聽(tīng)
}
},
methods: {
handleClickName() {
this.name = '姜子牙'
},
handleClickInfo() {
this.info.city = '上海'
}
}
}
</script>
官方告訴我們使用 watch 監(jiān)聽(tīng)復(fù)雜數(shù)據(jù)類型就需要用到深度監(jiān)聽(tīng) deep。
-
deep:為了發(fā)現(xiàn)對(duì)象內(nèi)部值的變化,可以在選項(xiàng)參數(shù)中指定 deep: true。注意監(jiān)聽(tīng)數(shù)組的變更不需要這么做。
我們針對(duì)上述代碼使用 deep 進(jìn)行重寫:
watch: {
info: {
handler(newVal, oldVal) {
console.log('watch info', newVal, oldVal) // {__ob__: Observer}
},
deep: true // 深度監(jiān)聽(tīng)
}
}
得到打印結(jié)果如下:

好吧!并沒(méi)有得到我們的預(yù)期,打印出來(lái)的
newVal 和 oldVal 值是一樣的,所以深度監(jiān)聽(tīng)雖然可以監(jiān)聽(tīng)到對(duì)象的變化,但是無(wú)法監(jiān)聽(tīng)到對(duì)象里面哪個(gè)具體屬性的變化。這是因?yàn)樗鼈兊囊弥赶蛲粋€(gè)對(duì)象/數(shù)組。Vue 不會(huì)保留變更之前值的副本。 vm.$watch 深度監(jiān)聽(tīng)
若果要監(jiān)聽(tīng)對(duì)象的單個(gè)屬性的變化,有兩種方法:
- 直接監(jiān)聽(tīng)對(duì)象的屬性
watch: {
'info.city'(newVal, oldVal) {
console.log('watch info', newVal, oldVal) // watch info 上海 北京
}
}
- 與
computed屬性配合使用,computed返回想要監(jiān)聽(tīng)的屬性值,watch用來(lái)監(jiān)聽(tīng)
computed: {
changeCity() {
return this.info.city
}
},
watch: {
changeCity(newVal, oldVal) {
console.log('watch info', newVal, oldVal) // watch info 上海 北京
}
}
上面的代碼都是我們定義了一個(gè)按鈕,通過(guò)按鈕手動(dòng)修改值來(lái)觸發(fā)值的變化進(jìn)而使用 watch 進(jìn)行監(jiān)聽(tīng),那么我們能不能在頁(yè)面初始化的時(shí)候就直接使用 watch 呢?認(rèn)識(shí) handler 方法和 immediate 屬性。
<p>姓名:{{ fullName }}</p> // 張三
<script>
data() {
return {
firstName: '張',
lastName: '三',
fullName: ''
}
},
watch: {
fullName: {
handler() {
this.fullName = this.firstName + this.lastName
},
immediate: true // 設(shè)置為 true,理解執(zhí)行 handle 方法
}
}
</script>
在 computed 中我們知道每一個(gè)屬性其實(shí)都有 get() 和 set(),在 watch 中每一個(gè)屬性其實(shí)都有 handle() 和 imediate 屬性(默認(rèn)為 false)。watch 的特點(diǎn)是最初綁定的時(shí)候是不會(huì)執(zhí)行的,而 immediate:true 代表如果在 wacth 里聲明了,那么就會(huì)立即先去執(zhí)行里面的 handler 方法。并且如果我們?cè)?computed 中如果定義了 fullName 那么在 data 里就不能重復(fù)定義 fullName,而 watch 則不受影響。
日常開(kāi)發(fā)中,我們還可以使用 watch 來(lái)監(jiān)聽(tīng)路由的變化,如下栗子:
watch: {
'$route'(to,from){
console.log(to); //to表示去往的界面
console.log(from); //from表示來(lái)自于哪個(gè)界面
if(to.path=="/shop/detail"){
console.log("商品詳情");
}
}
},
總結(jié):
watch和computed都是以Vue的依賴追蹤機(jī)制為基礎(chǔ)的,當(dāng)某一個(gè)依賴型數(shù)據(jù)發(fā)生變化的時(shí)候,所有依賴這個(gè)數(shù)據(jù)的相關(guān)數(shù)據(jù)會(huì)自動(dòng)發(fā)生變化,即自動(dòng)調(diào)用相關(guān)的函數(shù),來(lái)實(shí)現(xiàn)數(shù)據(jù)的變動(dòng)。當(dāng)依賴的值變化時(shí),在watch中,是可以做一些復(fù)雜的操作的,而computed中的依賴,僅僅是一個(gè)值依賴于另一個(gè)值,是值上的依賴。
兩者常用應(yīng)用場(chǎng)景區(qū)分:
computed:用于處理復(fù)雜的邏輯運(yùn)算;一個(gè)數(shù)據(jù)受一個(gè)或多個(gè)數(shù)據(jù)影響;用來(lái)處理watch和methods無(wú)法處理的,或處理起來(lái)不方便的情況。例如處理模板中的復(fù)雜表達(dá)式、購(gòu)物車?yán)锩娴纳唐窋?shù)量和總金額之間的變化關(guān)系等。
watch:用來(lái)處理當(dāng)一個(gè)屬性發(fā)生變化時(shí),需要執(zhí)行某些具體的業(yè)務(wù)邏輯操作,或要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開(kāi)銷較大的操作;一個(gè)數(shù)據(jù)改變影響多個(gè)數(shù)據(jù)。例如用來(lái)監(jiān)控路由、inpurt輸入框值的特殊處理等。
兩者主要的區(qū)別:
computed
- 初始化顯示或者相關(guān)的
data、props等屬性數(shù)據(jù)發(fā)生變化的時(shí)候調(diào)用 -
computed不支持異步; - 計(jì)算屬性不在
data中,它是基于data或props中的數(shù)據(jù)通過(guò)計(jì)算得到的一個(gè)新值,這個(gè)新值根據(jù)已知值的變化而變化; - 在
computed屬性對(duì)象中定義計(jì)算屬性的方法,和取data對(duì)象里的數(shù)據(jù)屬性一樣,以屬性訪問(wèn)的形式調(diào)用,但是computed中定義的屬性名稱不能和data中的屬性名稱重合; - 如果
computed屬性值是函數(shù),那么默認(rèn)會(huì)走get方法,必須要有一個(gè)返回值,函數(shù)的返回值就是屬性的屬性值; -
computed屬性值默認(rèn)會(huì)緩存計(jì)算結(jié)果,在重復(fù)的調(diào)用中,只要依賴數(shù)據(jù)不變,直接取緩存中的計(jì)算結(jié)果,只有依賴型數(shù)據(jù)發(fā)生改變,computed才會(huì)重新計(jì)算; - 在
computed中的,屬性都有一個(gè)get和一個(gè)set方法,當(dāng)數(shù)據(jù)變化時(shí),調(diào)用set方法。
watch
- 主要用來(lái)監(jiān)聽(tīng)某些特定數(shù)據(jù)的變化,從而進(jìn)行某些具體的業(yè)務(wù)邏輯操作,可以看作是
computed和methods的結(jié)合體; - 可以監(jiān)聽(tīng)的數(shù)據(jù)來(lái)源:
data,props,computed內(nèi)的數(shù)據(jù); -
watch支持異步; - 不支持緩存,監(jiān)聽(tīng)的數(shù)據(jù)改變,直接會(huì)觸發(fā)相應(yīng)的操作;
- 監(jiān)聽(tīng)函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)是最新的值,第二個(gè)參數(shù)是輸入之前的值,順序一定是新值,舊值。
如果文中有不對(duì)的地方或者理解有誤的地方歡迎大家提出并指正。每一天都要相對(duì)前一天進(jìn)步一點(diǎn),加油!?。?/p>