computed屬性
在模板中使用表達式是非常方便直接的,然而這只適用於簡單的操作。在模板中放入太多的邏輯,會使模板過度膨脹和難以維護。例如:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
在這個地方,模板不再簡潔和如聲明式直觀。你必須仔細觀察一段時間才能意識到,這里是想要顯示變量 message 的翻轉字符串。當你想要在模板中多次引用此處的翻轉字符串時,就會更加難以處理。
這就是為什么對於所有復雜邏輯,你都應該使用 computed 屬性(computed property)。
基礎實例
<body>
<div id="example">
<p>初始message是:"{{message}}"</p>
<p>計算后的翻轉message是:"{{reversedMessage}}"</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 一個computed屬性的getter函數
reversedMessage: function () {
// 'this'執行vm實例
return this.message.split('').reverse().join('')
}
}
})
</script>
</body>

這里我們聲明了一個 computed 屬性 reversedMessage。然后為 vm.reversedMessage 屬性提供一個函數,作為它的 getter 函數:
console.log(vm.reversedMessage) // => 'olleH' vm.message = 'Goodbye' console.log(vm.reversedMessage) // => 'eybdooG'
computed緩存vs method方法
可以在表達式中通過調用 method 方法的方式,也能夠實現與 computed 屬性相同的結果:
<body>
<div id="example">
<p>翻轉message是:"{{reverseMessage()}}"</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
methods: {
reverseMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
</script>
</body>
這里不使用 computed 屬性,而是在 methods 中定義一個相同的函數。對於最終結果,這兩種方式確實恰好相同。然而,細微的差異之處在於,computed 屬性會基於它所依賴的數據進行緩存。每個 computed 屬性,只有在它所依賴的數據發生變化時,才會重新取值(re-evaluate)。這就意味着,只要 message 沒有發生變化,多次訪問 computed 屬性 reversedMessage,將會立刻返回之前計算過的結果,而不必每次都重新執行函數。
這也同樣意味着,如下的 computed 屬性永遠不會更新,因為 Date.now() 不是一個響應式的依賴數據:
computed: {
now: function () {
return Date.now()
}
}
相比之下,每當觸發重新渲染(re-render)時,method 調用方式將總是再次執行函數。
為什么我們需要將依賴數據緩存起來?假設一種場景,我們有一個高性能開銷(expensive)的 computed 屬性 A,在 computed 屬性的 getter 函數內部,需要遍歷循環一個巨大數組,並進行大量計算。然后還有其他 computed 屬性直接或間接依賴於 A。如果沒有緩存,我們將不可避免地多次執行 A 的 getter 函數,這遠多余實際需要執行的次數!然而在某些場景下,你可能不希望有緩存,請使用 method 方法替代。
computed屬性和watch屬性
Vue 其實還提供了一種更加通用的方式,來觀察和響應 Vue 實例上的數據變化:watch 屬性。watch 屬性是很吸引人的使用方式,然而,當你有一些數據需要隨着另外一些數據變化時,過度濫用 watch 屬性會造成一些問題 - 尤其是那些具有 AngularJS 開發背景的開發人員。因此,更推薦的方式是,使用 computed 屬性,而不是命令式(imperative)的 watch 回調函數。思考下面這個示例:
<body>
<div id="demo">{{fullName}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
});
</script>
</body>
以上代碼是命令式和重復的。對比 computed 屬性實現的版本:
<body>
<div id="demo">{{fullName}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
</script>
</body>
computed屬性中設置setter
computed 屬性默認只設置 getter 函數,不過在需要時,還可以提供 setter 函數:
computed: {
fullName: {
// getter函數
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter函數
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
現在當你運行 vm.fullName = 'John Doe',將會調用 setter,然后會對應更新 vm.firstName 和 vm.lastName。
watcher
雖然在大多數情況下,更適合使用 computed 屬性,然而有些時候,還是需要一個自定義 watcher。這就是為什么 Vue 要通過 watch 選項,來提供一個更加通用的響應數據變化的方式。當你需要在數據變化響應時,執行異步操作,或高性能消耗的操作,自定義 watcher 的方式就會很有幫助。
<body>
<div id="watch-example">
<p>
問一個答案是yes/no的問題:
<input v-model="question">
</p>
<p>{{answer}}</p>
</div>
<!-- 對於 ajax 庫(ajax libraries)和通用工具方法的集合(collections of general-purpose utility methods)來說, -->
<!-- 由於已經存在大量與其相關的生態系統, -->
<!-- 因此 Vue 核心無需重新創造,以保持輕量的文件體積。 -->
<!-- 同時這也可以使你自由隨意地選擇自己最熟悉的。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: '你要先提出問題,我才能給你答案!'
},
watch: {
// 只要question發生改變,此函數就會執行
question: function (newQuestion, oldQuestion) {
this.answer = '等待輸入停止...'
this.getAnswer()
}
},
methods: {
// _.debounce 是由 lodash 提供的函數,
// 在運行特別消耗性能的操作時,
// 可以使用 _.debounce 來限制頻率。
// 在下面這種場景中,我們需要限制訪問 yesno.wtf/api 的頻率,
// 等到用戶輸入完畢之后,ajax 請求才會發出。
// 想要了解更多關於 _.debounce 函數(以及與它類似的 _.throttle)的詳細信息,
// 請訪問:https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = '問題通常需要包含一個中文問號。;-)'
return
}
this.answer = '思考中...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = '錯誤!API無法處理。' + error
})
},
// 這是用戶停止輸入操作后所等待的毫秒數。
// (譯者注:500毫秒之內,用戶繼續輸入,則重新計時)
500
)
}
})
</script>
</body>
在這個場景中,使用 watch 選項,可以使我們執行一個限制執行頻率的(訪問一個 API 的)異步操作,並且不斷地設置中間狀態,直到我們獲取到最終的 answer 數據之后,才真正執行異步操作。而 computed 屬性無法實現。
