computed 计算属性对比于 method:
- computed 响应式,method 非响应;computed 在调用时只有当其引用的响应式属性改变时才会调用函数重新计算值(存在缓存),而 methods 是一个函数,每次调用就是执行其函数式
- computed 中的成员可以只定义一个函数作为只读属性,也 可以自定义 get/set 作为读写属性
- computed 以 vue 对象的属性形式存在
在实际开发中,使用 computed 和 mothod 往往都能起到作用——返回或处理一个我们要的值,但是适用场景不同;比如:当我们要去时刻监控一个视图层对应的数据层的值的变化时,使用 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, 即存在 deep,则触发每个深层对象的依赖,追踪其变化。traverse 方法递归每一个对象或者数组,触发它们的 getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系。这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep.id 记录到 seenObjects,避免以后重复访问。
参考学习:
https://cn.vuejs.org/v2/guide…
https://juejin.im/post/5b87f1…