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

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