您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)Vue中Computed和Watch的作用是什么,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
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)并展示到模板中。
那最簡單的實(shí)現(xiàn)方式就是直接在模板中做這樣的轉(zhuǎn)化:
<template> <div id="app"> <p>{{message}}</p> <p>{{message.split('').reverse().join('')}}</p> </div> </template>
那這個(gè)時(shí)候,Vue官方告訴我們:過多的邏輯運(yùn)算會(huì)讓模板變得重且難以維護(hù),而且這種轉(zhuǎn)化無法復(fù)用,并指導(dǎo)我們使用計(jì)算屬性-computed來實(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í)有人肯定要說了,我用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)榇嬖诰彺?,只要message數(shù)據(jù)未發(fā)生變化,則多次訪問計(jì)算屬性對(duì)應(yīng)的函數(shù)只會(huì)執(zhí)行一次。
<template> <div id="app"> <p>{{message}}</p> <p>第一次訪問reverseMessage:{{reverseMessage}}</p> <p>第二次訪問reverseMessage:{{reverseMessage}}</p> <p>第三次訪問reverseMessage:{{reverseMessage}}</p> <p>第四次訪問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又名為偵聽屬性,它主要用于偵聽數(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)聽Vue data中的counter數(shù)據(jù) * 當(dāng)counter發(fā)生變化時(shí)會(huì)執(zhí)行對(duì)應(yīng)的偵聽函數(shù) * @param {*} newValue counter的新值 * @param {*} oldValue counter的舊值 * @return {*} None */ counter: function(newValue, oldValue){ if(this.counter == 10){ this.counter = 0; } } } } </script>
我們定義了一個(gè)偵聽屬性counter,該屬性偵聽的是Vue data中定義counter數(shù)據(jù),整個(gè)的邏輯就是點(diǎn)擊按鈕counter加1,當(dāng)counter等于10的時(shí)候,將counter置為0。
上面的代碼運(yùn)行后的結(jié)果如下:
Vue官網(wǎng)很明確的建議我們這樣使用watch偵聽屬性:當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),這個(gè)方式是最有用的。
4. computed和watch之間的抉擇
看完以上兩部分內(nèi)容,關(guān)于Vue中computed和watch的基本用法算是掌握了。但實(shí)際上不止這些,所以接下來我們?cè)趤磉M(jìn)階學(xué)習(xí)一波。
這里我們還原Vue官網(wǎng)中的一個(gè)示例,示例實(shí)現(xiàn)的功能大致如下:
該功能可以簡單的描述為:在firstName和lastName數(shù)據(jù)發(fā)生變化時(shí),對(duì)fullName進(jìn)行更新,其中fullName的值為firstName和lastName的拼接。
首先我們使用watch來實(shí)現(xiàn)該功能:watch偵聽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來實(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)方式更簡潔高級(jí)。
所以在日常項(xiàng)目開發(fā)中,對(duì)于computed和watch的使用要慎重選擇:
這兩者選擇和使用沒有對(duì)錯(cuò)之分,只是希望能更好的使用,而不是濫用。
5. 計(jì)算屬性進(jìn)階
接下來我們?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。
如果有閱讀過Vue源碼的同學(xué)對(duì)這個(gè)原因應(yīng)該會(huì)比較清楚,Vue在初始化的時(shí)候會(huì)按照:initProps-> initMethods -> initData -> initComputed -> initWatch這樣的順序?qū)?shù)據(jù)進(jìn)行初始化,并且會(huì)通過Object.definedProperty將數(shù)據(jù)定義到vm實(shí)例上,在這個(gè)過程中同名的屬性會(huì)被后面的同名屬性覆蓋。
通過打印組件實(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(''); } },
這種寫法實(shí)際上是給reverseMessage提供了一個(gè)get方法,所以上面的寫法等同于:
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)目開發(fā)中暫時(shí)還沒有遇到,不過經(jīng)過一番思考,做出來下面這樣一個(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é)過計(jì)算屬性存在緩存,并演示相關(guān)的示例。那計(jì)算屬性的緩存是如何實(shí)現(xiàn)的呢?
關(guān)于計(jì)算屬性的緩存這個(gè)知識(shí)點(diǎn)需要我們?nèi)ラ喿xVue的源碼實(shí)現(xiàn),所以我們一起來看看源碼吧。
相信大家看到源碼這個(gè)詞就會(huì)有點(diǎn)膽戰(zhàn)心驚,不過不用過分擔(dān)心,文章寫到這里的時(shí)候考慮到本篇文章的內(nèi)容和側(cè)重點(diǎn),所以不會(huì)詳細(xì)去解讀計(jì)算屬性的源碼,著重學(xué)習(xí)計(jì)算屬性的緩存實(shí)現(xiàn),并且點(diǎn)到為止。
那如果你沒有仔細(xì)解讀過Vue的響應(yīng)式原理,那建議忽略這一節(jié)的內(nèi)容,等對(duì)源碼中的響應(yīng)式有一定了解之后在來看這一節(jié)的內(nèi)容會(huì)更容易理解。(我自己之前也寫過的一篇相關(guān)文章:1W字長文+多圖,帶你了解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) // ......省略 ...... }
接著我們來看看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ì)算屬性,然后在遍歷的過程中做了下面兩件事:
第一件:為計(jì)算屬性創(chuàng)建watcher,即new Watcher
第二件:調(diào)用defineComputed方法
那首先我們先來看看new Watcher都做了什么。
為了方便大家看清楚new Watcher的作用,我將Watcher的源碼進(jìn)行了簡化,保留了一些比較重要的代碼。
同時(shí)代碼中重要的部分都添加了注釋,有些注釋描述的可能有點(diǎn)重復(fù)或者啰嗦,但主要是想以這種重復(fù)的方式讓大家可以反復(fù)琢磨并理解源碼中的內(nèi)容,方便后續(xù)的理解 ~
/* * Vue版本:v2.6.12 * 代碼位置: /vue/src/core/observer/watcher.js * 為了看清楚Watcher的作用 * 將源碼進(jìn)行簡化,所以下面是一個(gè)簡化版的Watcher類 * 同時(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類型 // 賦值之后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è)簡化版的Watcher以后,想必我們已經(jīng)很清楚的知道了Watcher類的實(shí)現(xiàn)。
那接下來就是關(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ì)算屬性變得可觀測 */ Object.defineProperty(target, key, sharedPropertyDefinition) }
defineComputed方法最核心也只有一行代碼,也就是使用Object.defineProperty將計(jì)算屬性變得可觀測。
那么接下來我們的關(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 }
前面貼出來的defineComputed源碼中,我注釋說明省略了一段有關(guān)sharedPropertyDefinition的代碼邏輯,那省略的這段源代碼就不展示了,它的主要作用就是在對(duì)sharedPropertyDefinition.get和sharedPropertyDefinition.set進(jìn)行重寫,重寫之后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)的值這里寫的是noop // 但是我們要知道set真正的值是我們?yōu)橛?jì)算屬性提供的set函數(shù) // 千萬不要理解錯(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í)。不過僅僅是了解這些還不夠,我們應(yīng)該去通讀計(jì)算屬性的完整源碼實(shí)現(xiàn),才能對(duì)計(jì)算屬性有一個(gè)更通透的認(rèn)識(shí)。
6. 偵聽屬性進(jìn)階
>>> 6.1 handler
前面我們是這樣實(shí)現(xiàn)偵聽屬性的:
watch: { counter: function(newValue, oldValue){ if(this.counter == 10){ this.counter = 0; } } }
那上面的這種寫法等同于給counter提供一個(gè)handler函數(shù):
watch: { counter: { handler: function(newValue, oldValue){ if(this.counter == 10){ this.counter = 0; } } } }
>>> 6.2 immediate
正常情況下,偵聽屬性提供的函數(shù)是不會(huì)立即執(zhí)行的,只有在對(duì)應(yīng)的vue data發(fā)生變化時(shí),偵聽屬性對(duì)應(yīng)的函數(shù)才會(huì)執(zhí)行。
那如果我們需要偵聽屬性對(duì)應(yīng)的函數(shù)立即執(zhí)行一次,就可以給偵聽屬性提供一個(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ì)象類型的vue data進(jìn)行偵聽,當(dāng)這個(gè)對(duì)象內(nèi)的屬性發(fā)生變化時(shí),默認(rèn)是不會(huì)觸發(fā)偵聽函數(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)聽對(duì)象類型的數(shù)據(jù),偵聽函數(shù)沒有觸發(fā):
通過給偵聽屬性提供deep: true就可以偵聽到對(duì)象內(nèi)部屬性的變化:
watch: { person: { handler: function(newValue){ console.log(newValue.name + ' ' + newValue.age); }, deep: true } }
不過仔細(xì)觀察上面的示例會(huì)發(fā)現(xiàn)這種方式去監(jiān)聽Object類型的數(shù)據(jù),Object數(shù)據(jù)內(nèi)部任一屬性發(fā)生變化都會(huì)觸發(fā)偵聽函數(shù),那如果我們想單獨(dú)偵聽對(duì)象中的某個(gè)屬性,可以使用下面這樣的方式:
watch: { 'person.name': function(newValue, oldValue){ // 邏輯 } }
以上就是Vue中Computed和Watch的作用是什么,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。