天天看點

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

做過vue開發的都知道,有兩個很重要的概念就是計算屬性和偵聽屬性,我們在平時開發當中會經常用到它們,以至于我們在面試的時候也會經常被問到這兩個之間的差別。

可能我們經常聽到或者知道的答案就是:

  1. computed有緩存,watch沒有緩存
  2. computed不支援異步,watch支援異步
  3. computed需要傳回值,watch不需要傳回值
  4. computed的值不能在data中定義,watch需要在data中定義

聽起來沒什麼問題,但是問題就在于你可能有一些疑惑,這些不同導緻它們看起來就是不同的東西,為什麼要放在一起進行比較呢?

我們可能會經常被誤導,導緻錯誤的認為這兩個屬性可以做相同的事情,它們隻是實作一個功能的不同用法,隻是在不同場景下各有優劣。

是的,我們不應該去分析它們的差別,更準确的說我們應該問計算屬性和偵聽屬性它們的特點是什麼?而真正可以與computed作比較的其實是屬于methods。

那麼接下來我們要做的,就是看一下computed、watch和methods有什麼特性,它們的用法都有哪些,以及computed和methods在實作相同功能的時候有哪些差別。

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

計算屬性computed

計算屬性的屬性名不需要在data中定義,表示這是一個由其他值計算得出的值,是以需要傳回值,隻不過當計算該值所用到的響應式依賴發生變化的時候,會通知該計算屬性進行重新計算,該屬性名會在收集的時候被當做新屬性存放起來,可以直接通過this.來擷取。

①當響應式依賴變化時會重新計算

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />單價:{{price}}<br />
    數量:{{count}}個<br />總價:{{totalPrice}}
    <button @click="addPrice">加1元</button>
    <button @click="addCount">加1個</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1
    }
  },
  computed: {
    totalPrice() {
      return this.count * this.price
    }
  },
  methods: {
    addPrice() {
      this.price++
    },
    addCount() {
      this.count++
    }
  }
}
</script>
<style scoped>
.pl-20px {
  padding-left: 20px;
}
.pr-20px {
  padding-top: 20px;
}
</style>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

我們可以看到初始化的時候totalPrice的值就已經被計算出來了,當count或price任意一個發生變化的時候,都會去重新計算totalPrice的值。

②當多次使用該值時,隻會計算一次,然後将值緩存起來,以供擷取,直到響應式依賴發生變化,才會重新計算

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    總價:{{totalPrice}}<br />
    總價:{{totalPrice}}<br />
    總價:{{totalPrice}}<br />
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1,
      nums: 0
    }
  },
  computed: {
    totalPrice() {
      console.log(`我被計算了${++this.nums}次`)
      return this.count * this.price
    }
  }
}
</script>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

計算屬性的緩存機制,主要是用來處理:如果一個值需要大量的計算才能得到,那麼當頁面中或者其他計算屬性或者其他地方用到該值的時候,隻需做一次計算即可多處使用,而不必重複計算,來避免過多的性能開銷,如果需要實時計算,不希望值被緩存,那麼使用方法就可以達到效果。

③可以傳入不同的參數,這個時候就是當做方法來調用,不再具有緩存的效果

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    總價:{{totalPrice("$")}}<br />
    總價:{{totalPrice("$")}}<br />
    總價:{{totalPrice("$")}}<br />
  </div>
</template>
<script>
let nums = 0
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1,
    }
  },
  computed: {
    totalPrice() {
      return function(sign) {
        console.log(`我被計算了${++nums}次`)
        return sign + this.count * this.price
      }
    }
  }
}
</script>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

通過傳回一個函數的方式,就可以對一個計算屬性進行傳參,不過此時該計算屬性就相當于使用了一個函數,不同的是,可以利用閉包的機制,來對所有的這些函數做一些初始化的操作,請看這個例子

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    總價:{{totalPrice("$")}}<br />
    總價:{{totalPrice("$")}}<br />
    總價:{{totalPrice("$")}}<br />
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 3,
    }
  },
  computed: {
    totalPrice() {
      let m = 0
      return function(sign) {
        return `優惠${m}件` + sign + (this.count - m++) * this.price
      }
    }
  }
}
</script>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

同一個計算屬性多次計算可以共享同一個閉包環境中的變量。

④可以指定getter和setter來控制行為

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    總價:{{totalPrice}}<br />
    <button @click="totalPrice += 10">總價+10</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1,
      nums: 0
    }
  },
  computed: {
    totalPrice: {
      get() {
        return this.count * this.price
      },
      set(val) {
        this.price = val / this.count
      }
    }
  }
}
</script>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

偵聽屬性watch

被偵聽的變量需要在data中定義,表示當該屬性值發生變化的時候,需要執行某些操作,是以支援函數所允許的任何行為,不需要傳回值,可以了解為這是對一個函數進行了監聽,相當于是給函數增加了addEventListener,觸發的條件就是偵聽的屬性發生了變化,然後就會執行定義的回調函數,接收兩個參數,第一個是改變後的值,第二個是改變之前的值。可以偵聽基本類型與引用類型,甚至能偵聽引用類型裡面的某個屬性。

①當偵聽一個基本類型時

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    總價:{{totalPrice}}<br />
    <button @click="count += 1">加1個</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1,
      totalPrice: 0
    }
  },
  watch: {
    count(nv, ov) {
      this.totalPrice = this.price * this.count
    }
  },
  created() {
    this.totalPrice = this.price * this.count
  }
}
</script>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

我們可以看到,當count屬性變化的時候,重新計算了totalPrice的值,這裡我們在created的時候,給totalPrice進行了指派,因為如果不初始化的話,totalPrice會取預設值0,我們把created這段代碼去掉,效果就像下面這樣

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

這是因為隻有當屬性變化的時候才會執行偵聽的函數,去計算totalPrice的值,那麼能不能在初始化的時候就直接執行一次偵聽的函數,而不用在created裡面重複的寫了呢?

當然可以,我們把偵聽函數改成用對象的形式,并設定immediate為true

watch: {
  count: {
    handler: function () {
      this.totalPrice = this.price * this.count
    },
    immediate: true
  }
}      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

②偵聽引用類型

<template>
  <div class="pl-20px pr-20px">
    <div v-for="item in goods">
      名稱:{{item.name}}<br />
      單價:{{item.price}}<br />
      數量:{{item.count}}個<br />
      <button @click="item.count += 1">加1個</button>
    </div>
    總價:{{totalPrice}}<br />
    <button @click="addGoods">追加物品</button>
    <button @click="changeGoods">改變物品</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      goods: [{
        name: '雪糕',
        price: 2,
        count: 1
      }],
      totalPrice: 0
    }
  },
  watch: {
    goods: {
      handler: function (nv) {
        this.totalPrice = nv.reduce((total, item) => {
          return total += item.price * item.count
        }, 0)
      },
      immediate: true
    }
  },
  methods: {
    addGoods() {
      this.goods.push({
        name: '飲料',
        price: 3,
        count: 2
      })
    },
    changeGoods() {
      this.goods = [{
        name: '辣條',
        price: 5,
        count: 6
      }]
    }
  }
}
</script>      

我們定義了一個引用類型的goods,設定了三種改變方式:更改對應項的數量、追加一種物品、更改定義的物品

初始化的時候我們看到立即執行發生了作用

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

增加雪糕的數量

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

我們發現總價并沒有發生改變,這說明該屬性的變化沒有被偵聽到,是以不會執行函數,那麼如何讓屬性的變化也能被偵聽到呢,就用到了deep屬性

watch: {
  goods: {
    handler: function (nv) {
      this.totalPrice = nv.reduce((total, item) => {
        return total += item.price * item.count
      }, 0)
    },
    immediate: true,
    deep: true
  }
}      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

這時我們看到屬性的變化也會引起偵聽函數的執行

追加一種物品

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

這個時候沒有deep屬性,也會偵聽到變化,進而執行函數

更改定義的物品

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

同樣會執行函數

③偵聽引用類型的某個屬性

<template>
  <div class="pl-20px pr-20px">
    <div v-for="item in goods">
      名稱:{{item.name}}<br />
      單價:{{item.price}}<br />
      數量:{{item.count}}個<br />
      <button @click="item.count += 1">加1個</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      goods: [{
        name: '雪糕',
        price: 2,
        count: 1
      }]
    }
  },
  watch: {
    'goods.0.count': function(nv) {
      console.log('現在的數量是:' + nv)
    }
  }
}
</script>      

當多次更改第一項的count值時,每次的變化都能夠被偵聽到

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

其實偵聽屬性不但可以設定為函數和對象,也可以設定為字元串和數組

當設定為字元串時,将會執行對應的方法

<template>
  <div class="pl-20px pr-20px">
    <div v-for="item in goods">
      名稱:{{item.name}}<br />
      單價:{{item.price}}<br />
      數量:{{item.count}}個<br />
    </div>
    <button @click="addGoods">追加物品</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      goods: [{
        name: '雪糕',
        price: 2,
        count: 1
      }]
    }
  },
  watch: {
    goods: 'goodsChanged'
  },
  methods: {
    addGoods() {
      this.goods.push({
        name: '飲料',
        price: 3,
        count: 2
      })
    },
    goodsChanged(nv, ov) {
      console.log('物品發生了改變')
      console.log(nv)
    }
  }
}
</script>      

點選追加物品按鈕,則會執行對應的方法,回調函數的兩個參數同樣是修改後與修改前的值

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

當設定為數組時,将會在偵聽屬性發生改變時,周遊這個數組,并把數組的每一項當做回調函數進行執行,數組的每一項同樣可以是函數、對象、字元串,注意不能再嵌套數組

<template>
  <div class="pl-20px pr-20px">
    <div v-for="item in goods">
      名稱:{{item.name}}<br />
      單價:{{item.price}}<br />
      數量:{{item.count}}個<br />
    </div>
    <button @click="addGoods">追加物品</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      goods: [{
        name: '雪糕',
        price: 2,
        count: 1
      }]
    }
  },
  watch: {
    goods: [
      'change1',
      function change2() {
        console.log('我是change2')
      },
      {
        handler: function change3() {
          console.log('我是change3')
        }
      }
    ]
  },
  methods: {
    addGoods() {
      this.goods.push({
        name: '飲料',
        price: 3,
        count: 2
      })
    },
    change1(nv, ov) {
      console.log('我是change1')
    }
  }
}
</script>      

點選追加物品按鈕,我們來看一下效果

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

方法methods

這個相信大家都已經能非常熟練的使用了,所有的方法都會預設挂載到目前執行個體上面,也就是說可以直接通過this.進行調用,這裡隻說一下當在template裡面使用時,我們能夠怎樣進行傳參

①預設傳參

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    共計:{{price * count}}
    <button @click="addCount">加1個</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1
    }
  },
  methods: {
    addCount(e) {
      console.log(e)
      this.count++
    }
  }
}
</script>      

當點選加1個按鈕時,會自動執行對應方法

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

并且會把事件對象作為參數傳遞進去

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

②主動傳參

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    共計:{{price * count}}
    <button @click="addCount(1)">加1個</button>
    <button @click="addCount(10)">加10個</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1
    }
  },
  methods: {
    addCount(ct) {
      this.count += ct
    }
  }
}
</script>      

在調用函數的地方,直接加上括号,然後把需要的參數傳遞進去就可以了,比如點選加10個,效果依然正常呈現

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

當然了這種方式就沒法擷取到目前的事件對象了,這時你可能會問,那我需要這個事件對象怎麼辦呢,我該如何去處理呢?别急,我們有辦法

通過$event來控制

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    共計:{{price * count}}
    <button @click="addCount(1, $event)">加1個</button>
    <button @click="addCount($event, 10)">加10個</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1
    }
  },
  methods: {
    addCount(a, b) {
      console.log('觀察參數')
      console.log(a)
      console.log(b)
    }
  }
}
</script>      

我們先點選加1個,再點選加10個

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

可以看到,我們可以輕易的控制參數的傳遞

還有一種就是通過包裹一層函數來控制

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    共計:{{price * count}}
    <button @click="(e) => addCount(1, e)">加1個</button>
    <button @click="(e) => addCount(e, 10)">加10個</button>
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 2,
      count: 1
    }
  },
  methods: {
    addCount(a, b) {
      console.log('觀察參數')
      console.log(a)
      console.log(b)
    }
  }
}
</script>      

我們先點選加1個,再點選加10個,來看下效果

計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

我們發現同樣實作了我們想要的效果

這樣方式對于使用自定義元件時尤其有用,我們不但可以接收來自元件傳遞的參數,而且可以傳入其他我們想要傳遞的參數

③作為計算屬性來使用

<template>
  <div class="pl-20px pr-20px">
    名稱:雪糕<br />
    單價:{{price}}<br />
    數量:{{count}}個<br />
    共計:{{totalPrice(price, count)}}
  </div>
</template>
<script>
export default {
  name: 'testComponent',
  data() {
    return {
      price: 5,
      count: 3
    }
  },
  methods: {
    totalPrice(p, c) {
      return p * c
    }
  }
}
</script>      
計算屬性computed和偵聽屬性watch有什麼差別?你會使用它們嗎?

這個時候并不是真正的計算屬性,而是跟計算屬性差不多,可以直接執行并傳回一個值,隻不過這種方式是沒有緩存的,因為此時this.totalPrice還是指向的方法,而寫成計算屬性的時候,this.totalPrice指向的是執行之後傳回的值,當響應式依賴發生改變的時候,vue會自動執行一遍,然後把this.totalPrice指向它的傳回值。

各自特點

相信看到這裡,你已經對computed、watch、methods大概有了一些了解,并清楚了它們各自的使用方式與特性,下面我們來重新歸納一下,看看你能不能了解呢

  • computed和methods無需在data中定義,watch需要
  • computed有緩存,是把該變量指向每次的執行結果,當響應式依賴變化時,vue會自動再次執行,而methods是把該變量指向函數,在用到的地方都會再次執行,而且當dom更新的時候,vue也會重新執行對應的方法,是以需要注意避免死循環,watch無所謂緩存不緩存
  • methods可以接收傳參,watch回調會把改變後與改變前的值傳遞過去,computed不能接收傳參,如果需要給computed進行傳參,那麼它此後的行為将于methods一緻
  • computed需要傳回值,methods可以有也可以沒有,當作為計算屬性使用時,需要傳回值,watch不需要傳回值
  • computed需要是同步方法,methods與watch可以執行異步方法,支援函數的所有行為
  • watch還可以在執行個體對象上手動添加,例如使用this.$watch添加更複雜的監聽,watch也可以監聽一個computed

總結

我們熟練的掌握了computed、watch、methods它們的特點與使用方式,才能夠在日常開發中得心應手應對一些複雜的場景,來幫助我們解決各種問題。

繼續閱讀