一篇文章帶你“深入”瞭解vue的計算屬性computed和偵聽屬器watch

計算屬性computed

設計插值表達式的初衷是爲了簡單運算的。但在模板中放入太多的邏輯會讓模板過重、難以維護且難以閱讀。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在這個地方,我們很難一眼看出來這個是想要得到反轉的字符串,如果我們有十個地方需要用到這種反轉字符串,就難以維護,並且臃腫,所以,對於任何複雜邏輯,我們都應當使用計算屬性

那麼計算屬性如何使用呢?

計算屬性computed是Vue配置對象中的屬性,使用方式如下:

<div id="app">
     <!-- 計算屬性的值可以像data數據一樣,直接被使用 -->
    <p>原始字符串: "{{ name }}"</p>
  	<p>翻轉字符串: "{{ getRverseName }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name:"young monk",
            age: 18
        },
        computed: {
            getRverseName(){// 返回的值,就是計算屬性的值
                return this.name.split("").reverse().join("")
            }
        }
    })
</script>

我們可以看到getRverseName的值取決於name,當我們改變name的時候,對應的getRverseName的值也會改變。

不知道你有沒有發現,我們使用方法一樣也可以實現上面的內容

<div id="app">
    <p>原始字符串: "{{ name }}"</p>
    <p>翻轉字符串: "{{ getRverseName() }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk",
            age: 18
        },
        methods: {
            getRverseName() {
                return this.name.split("").reverse().join("")
            }
        }
    })
</script>

我們不難看出,通過執行getRverseName()方法也可以得到相同的結果,那爲什麼還要有計算屬性呢?

雖然我們在插值表達式中調用方法也可以得到相同的效果,但是使用計算屬性和使用方法有着本質的區別。

當我們使用方法時,每一次頁面重新渲染,對應的方法都會執行一次。而計算屬性只有關聯的值改變時纔會重新執行。

比如說,我們改變age的值(vm.age = 17),頁面會重新渲染,這個時候getRverseName()方法會再次執行一次,但是我們並不需要該方法執行,因爲改動的數據和這個函數沒用任何關係,如果這個函數中有很複雜的邏輯和計算內容,性能開銷較打。但我們利用計算屬性,就不會出現這種情況。

所以,計算屬性和方法的最本質的區別,是:計算屬性是基於響應式依賴進行緩存的,計算屬性的值一直存於緩存中,只要它依賴的data數據不改變,每次訪問計算屬性,都會立刻返回緩存的結果,而不是再次執行函數。而方法則是每次觸發重新渲染,調用方法將總會再次執行函數。

深入計算屬性

計算屬性除了寫成一個函數之外,還可以寫成一個對象,對象內有兩個屬性,getter&setter,這兩個屬性皆爲函數,寫法如下:

const vm = new Vue({
  el: '#app',
  computed: {
    fullName: {
      getter () {
        // ...
      },
      setter () {
        // ...
      }
    }
  }
})

getter 讀取

在前面,我們直接將計算屬性寫成了一個函數,這個函數即爲get函數。也就是說,計算屬性默認只有getter。當我們去獲取某一個計算屬性時,就會執行get函數。getterthis,被自動綁定爲Vue實例

<div id="app">
    <p>原始字符串: "{{ name }}"</p>
  	<p>翻轉字符串: "{{ getRverseName }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name:"young monk",
            age: 18
        },
        computed: {
            getRverseName:{
            	get(){
         			return this.name.split("").reverse().join("")
    			}
        	}
           /*等同於
            getRverseName(){
                return this.name.split("").reverse().join("")
            }
           */
        }
    })
</script>

setter 設置

這是一個可選的選項,set函數會在給計算屬性重新賦值時會執行。參數是被重新設置的值。setterthis,被自動綁定爲Vue實例。

<div id="app">
    <p>原始字符串: "{{ name }}"</p>
  	<p>翻轉字符串: "{{ getRverseName }}"</p>
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name:"young monk",
            age: 18
        },
        computed: {
            getRverseName:{
            	get(){
                    return this.name.split("").reverse().join("")
                },
                set(value){
                    console.log("set執行了,值爲"+value)
                    this.name = value
                }
        	}
        }
    })
	vm.getReverseName = "the young monk" //重新賦值
</script>

注意,即使給計算屬性賦了值,計算屬性也不會重新計算,只有當依賴的響應式屬性變化了,計算屬性纔會重新計算。

偵聽器watch

雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。我們先來看看怎麼使用watch吧。

watch和computed一樣,是一個對象,但是watch對象中的寫法有很多,鍵的類型可以是一個正常的key值,也可以是一個字符串。值可以是一個函數、一個字符串、一個對象亦或者可以寫成一個數組。我來依次舉例,大家就明白了

值的不同寫法:

  • 函數類型:可以接收兩個參數,第一個參數是被改變的數據(新數據),第二個參數是賦值之前的數據(舊數據)
<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        },
        watch: {
            name(newValue, oldValue) {
                console.log(`新值:${newValue},舊值:${oldValue}`)
            }
        },
    })
    vm.name = "monk"  //更改name的值   新值:monk,舊值:young monk
</script>
  • 字符串類型:值爲方法名字,被偵聽的數據改變時,會執行該方法。
<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        },
        watch: {
            name:"nameChange"
        },
        methods: {
            nameChange(newValue, oldValue) {
                console.log(`新值:${newValue},舊值:${oldValue}`)
            }
        },
    })
    vm.name = "monk"
</script>
  • 對象類型:寫成對象類型時,可以提供選項。
    • handler:必需。handler時被偵聽的數據改變時執行的回調函數。handler的值類型爲函數/字符串,寫成字符串時爲一個方法的名字。
    • deep:在默認情況下,偵聽器偵聽對象只偵聽引用的變化,只有在給對象賦值時它才能被監聽到。所以需要使用deep選項,讓其可以發現對象內部值的變化,將deep的值設置爲true,那麼無論該對象被嵌套的有多深,都會被偵聽到。注意:當對象的屬性較多的時候,性能開銷會比較大,此時可以監聽對象的某個屬性
    • immediate:默認值是false,加上immediate選項後,回調將會在偵聽開始之後立刻被調用。而不是等待偵聽的數據更改後纔會調用,也就是說,當添加監聽時就會被調用一次
<div id="app">
    {{ info.name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            info: {
                name:"young monk"
            }
        },
        watch: {
            info: {
                handler() {
                    console.log("name的值被改變了")
                },
                deep:true,
                immediate:true
            }
        }
    })
    vm.info.name = "monk"
   /*會執行兩次,第一次是immediate的原因,第二次纔是因爲值的改變執行的
   	 name的值被改變了
     name的值被改變了
   */
</script>
  • 數組類型:可以將多種不同值類型寫在一個數組中。感覺花裏胡哨,毫無作用
<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        },
        watch: {
            name:[
                "nameChange",
                function(){
                    console.log("函數的方式執行了")
                },
                {
                    handler(){
                        console.log("對象的方式也執行了!")
                    },
                    deep:true,
                    immediate:true
                }
            ]
        },
        methods: {
            nameChange() {
                console.log("方法的方式也執行了")
            }
        }
    })
    vm.name = "monk"
</script>

鍵的不同寫法:

  • 正常對象key值

以上演示的都是正常的對象key值,這裏不再贅述。

  • 字符串類型key值:當key值類型爲字符串時,可以實現監聽對象當中的某一個屬性,如:
<div id="app">
    {{ info.name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            info:{
                name: "young monk"
            }
        },
        watch: {
            'info.name'(){
                console.log("對象的值改變啦")
            }
        }
    })
    vm.info.name = "monk"
    // 此時回調函數會執行,控制檯中打印出 ` 對象的值改變啦 `
</script> 

vm.$watch()

Vue實例將會在實例化時調用$watch,遍歷watch對象的每一個屬性。我們也可以利用vm.$watch來實現偵聽,用法與watch選項部分一致,略有不同。

  • 偵聽某個數據的變化,有兩種方式三個參數和兩個參數

1、三個參數,一參爲被偵聽的數據;二參爲數據改變時執行的回調函數;三參可選,爲設置的選項對象

<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        }
    })
    vm.$watch(
        'name',
        function () {
            console.log("name的值改變啦")
        }, 
        {
            deep: false,
            immediate: true
        })
</script>

2、二個參數,一參爲被偵聽的數據;二參爲選項對象,其中handler屬性爲必需,是數據改變時執行的回調函數,其他屬性可選。

<div id="app">
    {{ name }}
</div>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            name: "young monk"
        }
    })
    vm.$watch(
        'name',
        {
            handler(){
                console.log("name值改變啦")
            },
            deep:false,
            immediate:true
        })
</script>
  • 偵聽某個對象屬性的變化
vm.$watch('obj.name', /**參數和上面一樣*/)

當監聽的數據的在初始不確定,由多個數據得到時,此時可以將第一個參數寫成函數類型

vm.$watch(function () {
  // 表達式`this.firstName + this.lastName`每次得出一個不同的結果時該函數都會被調用
  // 這就像監聽一個未被定義的計算屬性
  return this.firstName + this.lastName;
}, /**參數和上面一樣*/)
  • 偵聽器函數執行後,會返回一個取消偵聽函數,用來停止觸發回調
const unwatch = vm.$watch('name', function () {});
unwatch(); // 執行後會取消偵聽name數據

使用unwatch時,需要注意的是,在帶有immediate選項時,不能在第一次回調時取消偵聽數據。如果仍然希望在回調內部用一個取消偵聽的函數,那麼可以先檢查該函數的可用性:

var unwatch = vm.$watch('msg', function () {
    // ...
    //unwatch  直接使用會報錯
    if(unwatch) {
      unwatch();  
    }
  },{
    immediate: true
  }
})

偵聽器 vs 計算屬性

  1. 兩者都可以觀察和響應Vue實例上的數據的變動。偵聽屬性可以監聽(data和computed)上的數據變化,因爲computed的屬性也會掛到data上

  2. watch擅長處理的場景是:一個數據影響多個數據。計算屬性擅長處理的場景是:多個數據影響一個數據。

  3. 在偵聽器中可以執行異步,但是在計算屬性中不可以

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章