computed
计算属性对比于methods
:
-
computed
响应式,而methods
非响应;computed
在调用时只有当其引用的响应式属性改变时才会调用函数重新计算值(存在缓存),而methods是一个函数,每次调用就是执行其函数式 -
computed
中的成员可以只定义一个函数作为只读属性,也可以自定义get/set
作为读写属性 -
computed
以vue对象的属性形式存在
在实际开发中,使用
computed
和
mothods
往往都能起到作用——返回或处理一个我们要的值,但是适用场景不同;比如:当我们要去时刻监控一个视图层对应的数据层的值的变化时,使用
computed
就比较合理了,因为
computed
可缓存的,只要数据层所依赖的值不改变,
computed
就不会改变,而只要变了 ,
computed
的值就会实时更新到视图层上,即
computed
是响应式的。而在这个例子中,如果使用
watch
也可以实现,但是那就是对视图层对应的数据层的值的依赖数据进行监听,发生变化时再调用相应的函数更改该值,那么watch
和computed
又有什么区别呢? 异同如下:
computed
计算属性对比于watch
侦听器:
- 相同点:都是vue对监听器的实现,都起到监听/依赖一个数据并进行处理的作用
- 在应用中,
computed
主要应用于同步数据,而watch
是用来观测一个值的变化去实现一段开销较大的复杂业务逻辑或执行异步操作 - 能用
computed
时优先使用computed
,避免当我们需要得到的值依赖于多个数据时多次调用watch的尴尬 watch
监听的值接收两个参数——新值、旧值 ,可以设置在初始化时调用例:
watch
:监听一个属性值的变化并执行相应的函数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 } } })
computed
:依赖其他属性所计算出来的值var vm = new Vue({ el:'#demo', data:{ firstName: 'Foo', lastName:'Bar' }, computed:{ fullName(){ return this.firstName+this.lastName; } } })
高级用法:
计算属性的setter
var vm = new Vue({
el:'#demo',
data:{
firstName:'Foo',
lastName:'Bar'
},
computed:{
fullName:{
get(){
return this.firstName + '·' + this.lastName
},
set(newVal){
var name = NewVal.split('·')
this.firstName = name[0];
this.lastName = name[name.length-1]
}
}
}
})
//运行vm.fullName = 'Kobe Bryant'时,set方法会被调用,vm.firstName和vm.lastName的值会被更新
侦听器的handler
方法和immediate
属性
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
console.log('第一次没有执行')
this.fullName = val + '·' + this.lastName
}
}
})
如果想firstName在第一次被绑定的时候就执行:
watch: {
firstName: {
handler(val){
console.log('第一次执行了')
this.fullName = val + '·' + this.lastName
},
immediate:true//在watch中声明后立即执行handler
}
}
侦听器的deep
属性
var vm = new Vue({
el:'#demo',
data:{
item:{
a:'',
b:''
}
},
watch:{
item:{
handler(val){
console.log('item.a changed')
},
immediate: true
}
}
})
//运行vm.item.a = '123',发现控制台没有打印“item.a changed”
改变item.a
的值发现控制台没有打印字符串,这是因为vue无法检测到对象属性的添加或者删除。由于vue会在初始化实例时给实例的属性执行getter/setter
转化过程,所以属性必须在data对象
上存在才能让vue转换它,才能是响应式的
默认情况下watch只监听对对象的引用,如当this.item = {a: '123',b:'123'}
执行时handler
就会执行,
深度遍历:
watch: {
obj: {
handler(val) {
console.log('item.a changed')
},
immediate: true,
deep: true
}
}
deep
的作用是:在对象一层层往下遍历,每一层都加上侦听器
优化
但是使用deep
属性会给每一层都加上监听器,性能开销可能就会非常大了。这样我们可以用字符串的形式来优化:
watch: {
'item.a': {
handler(val) {
console.log('item.a changed')
},
immediate: true
// deep: true
}
}
直到遇到item.a
属性,才会给该属性设置监听函数,提高性能。
源码实现:
/* @flow */
import { _Set as Set, isObject } from '../util/index'
import type { SimpleSet } from '../util/index'
import VNode from '../vdom/vnode'
const seenObjects = new Set()
/**
* Recursively traverse an object to evoke all converted
* getters, so that every nested property inside the object
* is collected as a "deep" dependency.
*/
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
如果设置this.deep == true
,则触发每个深层对象的依赖,追踪其变化。traverse
方法递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系。这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep.id 记录到 seenObjects
,避免以后重复访问。
参考学习: