詳解Vue中的Computed和Watch
本文轉(zhuǎn)載自微信公眾號(hào)「不知名寶藏程序媛 」,作者小土豆 。轉(zhuǎn)載本文請(qǐng)聯(lián)系不知名寶藏程序媛 公眾號(hào)。
1. 前言
作為一名Vue開(kāi)發(fā)者,雖然在項(xiàng)目中頻繁使用過(guò)computed和watch,但從來(lái)沒(méi)有系統(tǒng)的學(xué)習(xí)過(guò),總覺(jué)著對(duì)這兩個(gè)知識(shí)點(diǎn)有些一知半解。
如果你也和我一樣,就一起來(lái)回顧和總結(jié)一下這兩個(gè)知識(shí)點(diǎn)吧。
本篇非源碼分析,只是從兩者各自的用法、特性等做一些梳理。
2. Vue中的computed
Vue中的computed又叫做計(jì)算屬性,Vue官網(wǎng)中給了下面這樣一個(gè)示例。
模板中有一個(gè)message數(shù)據(jù)需要展示:
- <template>
- <div id="app">
- {{message}}
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- data() {
- return {
- message: 'Hello'
- }
- }
- }
- </script>
假如此時(shí)有一個(gè)需求:對(duì)message進(jìn)行反轉(zhuǎn)并展示到模板中。
那最簡(jiǎn)單的實(shí)現(xiàn)方式就是直接在模板中做這樣的轉(zhuǎn)化:
- <template>
- <div id="app">
- <p>{{message}}</p>
- <p>{{message.split('').reverse().join('')}}</p>
- </div>
- </template>
那這個(gè)時(shí)候,Vue官方告訴我們:過(guò)多的邏輯運(yùn)算會(huì)讓模板變得重且難以維護(hù),而且這種轉(zhuǎn)化無(wú)法復(fù)用,并指導(dǎo)我們使用計(jì)算屬性-computed來(lái)實(shí)現(xiàn)這個(gè)需求。
- export default {
- name: 'App',
- computed: {
- reverseMessage: function(){
- return this.message.split('').reverse().join('');
- }
- },
- data() {
- return {
- message: 'Hello'
- }
- }
- }
在以上代碼中我們定義了一個(gè)計(jì)算屬性:reverseMessage,其值為一個(gè)函數(shù)并返回我們需要的結(jié)果。
之后在模板中就可以像使用message一樣使用reverseMessage。
- <template>
- <div id="app">
- <p>{{message}}</p>
- <p>{{reverseMessage}}</p>
- </div>
- </template>
那么此時(shí)有人肯定要說(shuō)了,我用methods也能實(shí)現(xiàn)呀。確實(shí)使用methods也能實(shí)現(xiàn)此種需求,但是在這種情況下我們的計(jì)算屬性相較于methods是有很大優(yōu)勢(shì)的,這個(gè)優(yōu)勢(shì)就是計(jì)算屬性存在緩存。
如果我們使用methods實(shí)現(xiàn)前面的需求,當(dāng)message的反轉(zhuǎn)結(jié)果有多個(gè)地方在使用,對(duì)應(yīng)的methods函數(shù)會(huì)被調(diào)用多次,函數(shù)內(nèi)部的邏輯也需要執(zhí)行多次;而計(jì)算屬性因?yàn)榇嬖诰彺妫灰猰essage數(shù)據(jù)未發(fā)生變化,則多次訪(fǎng)問(wèn)計(jì)算屬性對(duì)應(yīng)的函數(shù)只會(huì)執(zhí)行一次。
- <template>
- <div id="app">
- <p>{{message}}</p>
- <p>第一次訪(fǎng)問(wèn)reverseMessage:{{reverseMessage}}</p>
- <p>第二次訪(fǎng)問(wèn)reverseMessage:{{reverseMessage}}</p>
- <p>第三次訪(fǎng)問(wèn)reverseMessage:{{reverseMessage}}</p>
- <p>第四次訪(fǎng)問(wèn)reverseMessage:{{reverseMessage}}</p>
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- computed: {
- reverseMessage: function(value){
- console.log(" I'm reverseMessage" )
- return this.message.split('').reverse().join('');
- }
- },
- data() {
- return {
- message: 'Hello'
- }
- }
- }
- </script>
運(yùn)行項(xiàng)目,查看結(jié)果,會(huì)發(fā)現(xiàn)計(jì)算屬性reverseMessage對(duì)應(yīng)的函數(shù)只執(zhí)行了一次。

3. Vue中的watchVue
中的watch又名為偵聽(tīng)屬性,它主要用于偵聽(tīng)數(shù)據(jù)的變化,在數(shù)據(jù)發(fā)生變化的時(shí)候執(zhí)行一些操作。
- <template>
- <div id="app">
- <p>計(jì)數(shù)器:{{counter}}</p>
- <el-button type="primary" @click="counter++">
- Click
- </el-button>
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- data() {
- return {
- counter: 0
- }
- },
- watch: {
- /**
- * @name: counter
- * @description:
- * 監(jiān)聽(tīng)Vue data中的counter數(shù)據(jù)
- * 當(dāng)counter發(fā)生變化時(shí)會(huì)執(zhí)行對(duì)應(yīng)的偵聽(tīng)函數(shù)
- * @param {*} newValue counter的新值
- * @param {*} oldValue counter的舊值
- * @return {*} None
- */
- counter: function(newValue, oldValue){
- if(this.counter == 10){
- this.counter = 0;
- }
- }
- }
- }
- </script>
我們定義了一個(gè)偵聽(tīng)屬性counter,該屬性偵聽(tīng)的是Vue data中定義counter數(shù)據(jù),整個(gè)的邏輯就是點(diǎn)擊按鈕counter加1,當(dāng)counter等于10的時(shí)候,將counter置為0。
上面的代碼運(yùn)行后的結(jié)果如下:

Vue官網(wǎng)很明確的建議我們這樣使用watch偵聽(tīng)屬性:當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開(kāi)銷(xiāo)較大的操作時(shí),這個(gè)方式是最有用的。
4. computed和watch之間的抉擇
看完以上兩部分內(nèi)容,關(guān)于Vue中computed和watch的基本用法算是掌握了。但實(shí)際上不止這些,所以接下來(lái)我們?cè)趤?lái)進(jìn)階學(xué)習(xí)一波。
這里我們還原Vue官網(wǎng)中的一個(gè)示例,示例實(shí)現(xiàn)的功能大致如下:

該功能可以簡(jiǎn)單的描述為:在firstName和lastName數(shù)據(jù)發(fā)生變化時(shí),對(duì)fullName進(jìn)行更新,其中fullName的值為firstName和lastName的拼接。
首先我們使用watch來(lái)實(shí)現(xiàn)該功能:watch偵聽(tīng)firstName和lastName,當(dāng)這兩個(gè)數(shù)據(jù)發(fā)生變化時(shí)更新fullName的值。
- <template>
- <div id="app">
- <p>firstName: <el-input v-model="firstName" placeholder="請(qǐng)輸入firstName"></el-input></p>
- <p>lastName: <el-input v-model="lastName" placeholder="請(qǐng)輸入lastName"></el-input></p>
- <p>fullName: {{fullName}}</p>
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- data() {
- return {
- firstName: '',
- lastName: '',
- fullName: '(空)'
- }
- },
- // 使用watch實(shí)現(xiàn)
- watch: {
- firstName: function(newValue) {
- this.fullName = newValue + ' ' + this.lastName;
- },
- lastName: function(newValue){
- this.fullName = this.firstName + ' ' + newValue;
- }
- }
- }
- </script>
接著我們?cè)谑褂胏omputed來(lái)實(shí)現(xiàn):定義計(jì)算屬性fullName,將firstName和lastName的值進(jìn)行拼接并返回。
- <template>
- <div id="app">
- <p>firstName: <el-input v-model="firstName" placeholder="請(qǐng)輸入firstName"></el-input></p>
- <p>lastName: <el-input v-model="lastName" placeholder="請(qǐng)輸入lastName"></el-input></p>
- <p>fullName: {{fullName}}</p>
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- data() {
- return {
- firstName: '',
- lastName: ''
- }
- }
- computed: {
- fullName: function() {
- return this.firstName + ' ' + this.lastName;
- }
- }
- }
- </script>
我們發(fā)現(xiàn)computed和watch都可以實(shí)現(xiàn)這個(gè)功能,但是我們?cè)趯?duì)比一下這兩種不同的實(shí)現(xiàn)方式:
- // 使用computed實(shí)現(xiàn)
- computed: {
- fullName: function() {
- return this.firstName + ' ' + this.lastName;
- }
- },
- // 使用watch實(shí)現(xiàn)
- watch: {
- firstName: function(newValue) {
- this.fullName = newValue + ' ' + this.lastName;
- },
- lastName: function(newValue){
- this.fullName = this.firstName + ' ' + newValue;
- }
- }
對(duì)比之下很明顯的會(huì)發(fā)現(xiàn)發(fā)現(xiàn)computed的實(shí)現(xiàn)方式更簡(jiǎn)潔高級(jí)。
所以在日常項(xiàng)目開(kāi)發(fā)中,對(duì)于computed和watch的使用要慎重選擇:

這兩者選擇和使用沒(méi)有對(duì)錯(cuò)之分,只是希望能更好的使用,而不是濫用。
5. 計(jì)算屬性進(jìn)階
接下來(lái)我們?cè)趯?duì)計(jì)算屬性的內(nèi)容進(jìn)行進(jìn)階學(xué)習(xí)。
>>> 5.1 計(jì)算屬性不能和Vue Data屬性同名
在聲明計(jì)算屬性的時(shí)候,計(jì)算屬性是不能和Vue Data中定義的屬性同名,否則會(huì)出現(xiàn)錯(cuò)誤:The computed property "xxxxx" is already defined in data。
如果有閱讀過(guò)Vue源碼的同學(xué)對(duì)這個(gè)原因應(yīng)該會(huì)比較清楚,Vue在初始化的時(shí)候會(huì)按照:initProps-> initMethods -> initData -> initComputed -> initWatch這樣的順序?qū)?shù)據(jù)進(jìn)行初始化,并且會(huì)通過(guò)Object.definedProperty將數(shù)據(jù)定義到vm實(shí)例上,在這個(gè)過(guò)程中同名的屬性會(huì)被后面的同名屬性覆蓋。
通過(guò)打印組件實(shí)例對(duì)象,可以很清楚的看到props、methods、data、computed會(huì)被定義到vm實(shí)例上。
>>> 5.2 計(jì)算屬性的set函數(shù)
在前面代碼示例中,我們的computed是這么實(shí)現(xiàn)的:
- computed: {
- reverseMessage: function(){
- return this.message.split('').reverse().join('');
- }
- },
這種寫(xiě)法實(shí)際上是給reverseMessage提供了一個(gè)get方法,所以上面的寫(xiě)法等同于:
- computed: {
- reverseMessage: {
- // 計(jì)算屬性的get方法
- get: function(){
- return this.message.split('').reverse().join('');
- }
- }
- },
除此之外,我們也可以給計(jì)算屬性提供一個(gè)set方法:
- computed: {
- reverseMessage: {
- // 計(jì)算屬性的get方法
- get: function(){
- return this.message.split('').reverse().join('');
- },
- set: function(newValue){
- // set方法的邏輯
- }
- }
- },
只有我們主動(dòng)修改了計(jì)算屬性的值,set方法才會(huì)被觸發(fā)。
關(guān)于計(jì)算屬性的set方法在實(shí)際的項(xiàng)目開(kāi)發(fā)中暫時(shí)還沒(méi)有遇到,不過(guò)經(jīng)過(guò)一番思考,做出來(lái)下面這樣一個(gè)示例:

這個(gè)示例是分鐘和小時(shí)之間的一個(gè)轉(zhuǎn)化,利用計(jì)算屬性的set方法就能很好實(shí)現(xiàn):
- <template>
- <div id="app">
- <p>分鐘<el-input v-model="minute" placeholder="請(qǐng)輸入內(nèi)容"></el-input></p>
- <p>小時(shí)<el-input v-model="hours" placeholder="請(qǐng)輸入內(nèi)容"></el-input></p>
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- data() {
- return {
- minute: 60,
- }
- },
- computed: {
- hours:{
- get: function() {
- return this.minute / 60;
- },
- set: function(newValue) {
- this.minute = newValue * 60;
- }
- }
- }
- }
- </script>
>>> 5.3 計(jì)算屬性的緩存
前面我們總結(jié)過(guò)計(jì)算屬性存在緩存,并演示相關(guān)的示例。那計(jì)算屬性的緩存是如何實(shí)現(xiàn)的呢?
關(guān)于計(jì)算屬性的緩存這個(gè)知識(shí)點(diǎn)需要我們?nèi)ラ喿xVue的源碼實(shí)現(xiàn),所以我們一起來(lái)看看源碼吧。
相信大家看到源碼這個(gè)詞就會(huì)有點(diǎn)膽戰(zhàn)心驚,不過(guò)不用過(guò)分擔(dān)心,文章寫(xiě)到這里的時(shí)候考慮到本篇文章的內(nèi)容和側(cè)重點(diǎn),所以不會(huì)詳細(xì)去解讀計(jì)算屬性的源碼,著重學(xué)習(xí)計(jì)算屬性的緩存實(shí)現(xiàn),并且點(diǎn)到為止。
那如果你沒(méi)有仔細(xì)解讀過(guò)Vue的響應(yīng)式原理,那建議忽略這一節(jié)的內(nèi)容,等對(duì)源碼中的響應(yīng)式有一定了解之后在來(lái)看這一節(jié)的內(nèi)容會(huì)更容易理解。(我自己之前也寫(xiě)過(guò)的一篇相關(guān)文章:1W字長(zhǎng)文+多圖,帶你了解vue2.x的雙向數(shù)據(jù)綁定源碼實(shí)現(xiàn),點(diǎn)擊文末閱讀原文即可查看)
關(guān)于計(jì)算屬性的入口源代碼如下:
- /*
- * Vue版本:v2.6.12
- * 代碼位置:/vue/src/core/instance/state.js
- */
- export function initState (vm: Component) {
- // ......省略......
- const opts = vm.$options
- // ......省略......
- if (opts.computed) initComputed(vm, opts.computed)
- // ......省略 ......
- }
接著我們來(lái)看看initComputed:
- /*
- * Vue版本:v2.6.12
- * 代碼位置:/vue/src/core/instance/state.js
- * @params: vm vue實(shí)例對(duì)象
- * @params: computed 所有的計(jì)算屬性
- */
- function initComputed (vm: Component, computed: Object) {
- /*
- * Object.create(null):創(chuàng)建一個(gè)空對(duì)象
- * 定義的const watchers是用于保存所有計(jì)算屬性的Watcher實(shí)例
- */
- const watchers = vm._computedWatchers = Object.create(null)
- // 遍歷計(jì)算屬性
- for (const key in computed) {
- const userDef = computed[key]
- /*
- * 獲取計(jì)算屬性的get方法
- * 計(jì)算屬性可以是function,默認(rèn)提供的是get方法
- * 也可以是對(duì)象,分別聲明get、set方法
- */
- const getter = typeof userDef === 'function' ? userDef : userDef.get
- /*
- * 給計(jì)算屬性創(chuàng)建watcher
- * @params: vm vue實(shí)例對(duì)象
- * @params: getter 計(jì)算屬性的get方法
- * @params: noop
- noop是定義在 /vue/src/shared/util.js中的一個(gè)函數(shù)
- export function noop (a?: any, b?: any, c?: any) {}
- * @params: computedWatcherOptions
- * computedWatcherOptions是一個(gè)對(duì)象,定義在本文件的167行
- * const computedWatcherOptions = { lazy: true }
- */
- watchers[key] = new Watcher(
- vm,
- getter || noop,
- noop,
- computedWatcherOptions
- )
- // 函數(shù)調(diào)用
- defineComputed(vm, key, userDef)
- }
- }
在initComputed這個(gè)函數(shù)中,主要是遍歷計(jì)算屬性,然后在遍歷的過(guò)程中做了下面兩件事:
- 第一件:為計(jì)算屬性創(chuàng)建watcher,即new Watcher
- 第二件:調(diào)用defineComputed方法
那首先我們先來(lái)看看new Watcher都做了什么。
為了方便大家看清楚new Watcher的作用,我將Watcher的源碼進(jìn)行了簡(jiǎn)化,保留了一些比較重要的代碼。
同時(shí)代碼中重要的部分都添加了注釋?zhuān)行┳⑨屆枋龅目赡苡悬c(diǎn)重復(fù)或者啰嗦,但主要是想以這種重復(fù)的方式讓大家可以反復(fù)琢磨并理解源碼中的內(nèi)容,方便后續(xù)的理解 ~
- /*
- * Vue版本:v2.6.12
- * 代碼位置: /vue/src/core/observer/watcher.js
- * 為了看清楚Watcher的作用
- * 將源碼進(jìn)行簡(jiǎn)化,所以下面是一個(gè)簡(jiǎn)化版的Watcher類(lèi)
- * 同時(shí)部分代碼順序有所調(diào)整
- */
- export default class Watcher {
- constructor (
- vm: Component,
- expOrFn: string | Function,
- cb: Function,
- options?: ?Object,
- ) {
- // vm為組件實(shí)例
- this.vm = vm
- // expOrFn在new Watcher時(shí)傳遞的參數(shù)為計(jì)算屬性的get方法
- // 將計(jì)算屬性的get方法賦值給watcher的getter屬性
- this.getter = expOrFn
- // cb為noop:export function noop (a?: any, b?: any, c?: any) {}
- this.cb = cb
- // option在new Watcher傳遞的參數(shù)值為{lazy: true}
- // !!操作符即將options.lazy強(qiáng)轉(zhuǎn)為boolean類(lèi)型
- // 賦值之后this.lazy的值為true
- this.lazy = !!options.lazy
- // 賦值之后this.dirty的值true
- this.dirty = this.lazy
- /*
- * 在new Watcher的時(shí)候因?yàn)閠his.lazy的值為true
- * 所以this.value的值還是undefined
- */
- this.value = this.lazy ? undefined : this.get()
- }
- get () {
- const vm = this.vm
- /*
- * 在構(gòu)造函數(shù)中,計(jì)算屬性的get方法賦值給了watcher的getter屬性
- * 所以該行代碼即調(diào)用計(jì)算屬性的get方法,獲取計(jì)算屬性的值
- */
- value = this.getter.call(vm, vm)
- return value
- }
- evaluate () {
- /*
- * 調(diào)用watcher的get方法
- * watcher的get方法邏輯為:調(diào)用計(jì)算屬性的get方法獲取計(jì)算屬性的值并返回
- * 所以evaluate函數(shù)也就是獲取計(jì)算屬性的值,并賦值給watcher.value
- * 并且將watcher.dirty置為false,這個(gè)dirty是實(shí)現(xiàn)緩存的關(guān)鍵
- */
- this.value = this.get()
- this.dirty = false
- }
- }
看了這個(gè)簡(jiǎn)化版的Watcher以后,想必我們已經(jīng)很清楚的知道了Watcher類(lèi)的實(shí)現(xiàn)。
那接下來(lái)就是關(guān)于緩存的重點(diǎn)了,也就是遍歷計(jì)算屬性做的第二件事:調(diào)用defineComputed函數(shù):
- /*
- * Vue版本:v2.6.12
- * 代碼位置:/vue/src/core/instance/state.js
- * @params: target vue實(shí)例對(duì)象
- * @params: key 計(jì)算屬性名
- * @params: userDef 計(jì)算屬性定義的function或者object
- */
- export function defineComputed (
- target: any,
- key: string,
- userDef: Object | Function
- ) {
- // ......暫時(shí)省略有關(guān)sharedPropertyDefinition的代碼邏輯......
- /*
- * sharedPropertyDefinition本身是一個(gè)對(duì)象,定義在本文件31行:
- * const sharedPropertyDefinition = {
- * enumerable: true,
- * configurable: true,
- * get: noop,
- * set: noop
- * }
- * 最后使用Object.defineProperty傳入對(duì)應(yīng)的參數(shù)使得計(jì)算屬性變得可觀測(cè)
- */
- Object.defineProperty(target, key, sharedPropertyDefinition)
- }
defineComputed方法最核心也只有一行代碼,也就是使用Object.defineProperty將計(jì)算屬性變得可觀測(cè)。
那么接下來(lái)我們的關(guān)注點(diǎn)就是調(diào)用Object.defineProperty函數(shù)時(shí)傳遞的第三個(gè)參數(shù):sharedPropertyDefinition。
sharedPropertyDefinition是定義在當(dāng)前文件中的一個(gè)對(duì)象,默認(rèn)值如下:
- const sharedPropertyDefinition = {
- enumerable: true,
- configurable: true,
- get: noop,
- set: noop
- }
前面貼出來(lái)的defineComputed源碼中,我注釋說(shuō)明省略了一段有關(guān)sharedPropertyDefinition的代碼邏輯,那省略的這段源代碼就不展示了,它的主要作用就是在對(duì)sharedPropertyDefinition.get和sharedPropertyDefinition.set進(jìn)行重寫(xiě),重寫(xiě)之后sharedPropertyDefinition的值為:
- const sharedPropertyDefinition = {
- enumerable: true,
- configurable: true,
- get: function(){
- // 獲取計(jì)算屬性對(duì)應(yīng)的watcher實(shí)例
- const watcher = this._computedWatchers && this._computedWatchers[key]
- if (watcher) {
- if (watcher.dirty) {
- watcher.evaluate()
- }
- if (Dep.target) {
- watcher.depend()
- }
- return watcher.value
- }
- }
- },
- // set對(duì)應(yīng)的值這里寫(xiě)的是noop
- // 但是我們要知道set真正的值是我們?yōu)橛?jì)算屬性提供的set函數(shù)
- // 千萬(wàn)不要理解錯(cuò)了哦
- set: noop,
- }
那sharedPropertyDefinition.get函數(shù)的邏輯已經(jīng)非常的清晰了,同時(shí)它的邏輯就是計(jì)算屬性緩存實(shí)現(xiàn)的關(guān)鍵邏輯:在sharedPropertyDefinition.get函數(shù)中,先獲取到計(jì)算屬性對(duì)應(yīng)的watcher實(shí)例;然后判斷watcher.dirty的值,如果該值為false,則直接返回watcher.value;否則調(diào)用watcher.evaluate()重新獲取計(jì)算屬性的值。
關(guān)于計(jì)算屬性緩存的源碼分析就到這里,相信大家對(duì)計(jì)算屬性的緩存實(shí)現(xiàn)已經(jīng)有了一定的認(rèn)識(shí)。不過(guò)僅僅是了解這些還不夠,我們應(yīng)該去通讀計(jì)算屬性的完整源碼實(shí)現(xiàn),才能對(duì)計(jì)算屬性有一個(gè)更通透的認(rèn)識(shí)。
6. 偵聽(tīng)屬性進(jìn)階
>>> 6.1 handler
前面我們是這樣實(shí)現(xiàn)偵聽(tīng)屬性的:
- watch: {
- counter: function(newValue, oldValue){
- if(this.counter == 10){
- this.counter = 0;
- }
- }
- }
那上面的這種寫(xiě)法等同于給counter提供一個(gè)handler函數(shù):
- watch: {
- counter: {
- handler: function(newValue, oldValue){
- if(this.counter == 10){
- this.counter = 0;
- }
- }
- }
- }
>>> 6.2 immediate
正常情況下,偵聽(tīng)屬性提供的函數(shù)是不會(huì)立即執(zhí)行的,只有在對(duì)應(yīng)的vue data發(fā)生變化時(shí),偵聽(tīng)屬性對(duì)應(yīng)的函數(shù)才會(huì)執(zhí)行。
那如果我們需要偵聽(tīng)屬性對(duì)應(yīng)的函數(shù)立即執(zhí)行一次,就可以給偵聽(tīng)屬性提供一個(gè)immediate選項(xiàng),并設(shè)置其值為true。
- watch: {
- counter: {
- handler: function(newValue, oldValue){
- if(this.counter == 10){
- this.counter = 0;
- }
- },
- immediate: true
- }
- }
>>> 6.3 deep
如果我們對(duì)一個(gè)對(duì)象類(lèi)型的vue data進(jìn)行偵聽(tīng),當(dāng)這個(gè)對(duì)象內(nèi)的屬性發(fā)生變化時(shí),默認(rèn)是不會(huì)觸發(fā)偵聽(tīng)函數(shù)的。
- <template>
- <div id="app">
- <p><el-input v-model="person.name" placeholder="請(qǐng)輸入姓名"></el-input></p>
- <p><el-input v-model="person.age" placeholder="請(qǐng)輸入年齡"></el-input></p>
- </div>
- </template>
- <script>
- export default {
- name: 'App',
- data() {
- return {
- person: {
- name: 'jack',
- age: 20
- }
- }
- },
- watch: {
- person: function(newValue){
- console.log(newValue.name + ' ' + newValue.age);
- }
- }
- }
- </script>
監(jiān)聽(tīng)對(duì)象類(lèi)型的數(shù)據(jù),偵聽(tīng)函數(shù)沒(méi)有觸發(fā):

通過(guò)給偵聽(tīng)屬性提供deep: true就可以偵聽(tīng)到對(duì)象內(nèi)部屬性的變化:
- watch: {
- person: {
- handler: function(newValue){
- console.log(newValue.name + ' ' + newValue.age);
- },
- deep: true
- }
- }

不過(guò)仔細(xì)觀察上面的示例會(huì)發(fā)現(xiàn)這種方式去監(jiān)聽(tīng)Object類(lèi)型的數(shù)據(jù),Object數(shù)據(jù)內(nèi)部任一屬性發(fā)生變化都會(huì)觸發(fā)偵聽(tīng)函數(shù),那如果我們想單獨(dú)偵聽(tīng)對(duì)象中的某個(gè)屬性,可以使用下面這樣的方式:
- watch: {
- 'person.name': function(newValue, oldValue){
- // 邏輯
- }
- }
7.總結(jié)
到此本篇文章就結(jié)束了,內(nèi)容非常的簡(jiǎn)單易懂,在此將以上的內(nèi)容做以總結(jié):

學(xué)無(wú)止境,除了基礎(chǔ)的內(nèi)容之外,很多特性的實(shí)現(xiàn)原理也是我們應(yīng)該關(guān)注的東西,但是介于本篇文章輸出的初衷,所以對(duì)原理實(shí)現(xiàn)并沒(méi)有完整的分析,后面有機(jī)會(huì)在總結(jié)~
原文鏈接:https://mp.weixin.qq.com/s/frWBXTa-EnhkGFn6c1X5NQ