持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天。
通过下面一个例子我们来分析computed和watch的区别。
var vm = new Vue({
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
watch: {
'firstName': function(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
})
computed
首先我们看下精简后的computed的源码
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
我们发现initComputed做了三件事情
- 创建空的watchers对象
- 遍历computed对象,根据getter函数创建一个watcher实例,然后存到watchers对象当中
- 调用defineComputed函数
根据我们的例子, 我们创建出来的watchers基本如下:
watchers: {
fullName: new Watcher(
vm,
function () {
return this.firstName + ' ' + this.lastName
},
noop,
computedWatcherOptions
)
}
接着分析Watcher构造函数, 我们看下精简后的watcher构造函数
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
...
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.get()
}
get () {
let value
const vm = this.vm
value = this.getter.call(vm, vm)
return value
}
}
通过this.getter.call(vm, vm)获取到结果赋值给this.value
根据我们的例子可以获取到这样一个结果
watchers: {
fullName: {
value: 'Foo Bar',
...
}
}
我们接着看精简后的defineComputed函数
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
sharedPropertyDefinition.get = createComputedGetter(key)
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
return watcher.value
}
}
}
defineComputed做了一件事情,通过defineProperty代理到computedGetter方法。
我们可以总结一下computed:
- 根据getter函数创建watcher实例,存到watchers对象当中
- 通过defineProperty代理访问watchers对象中watcher实例的value属性
根据我们的例子,每次访问this.fullName, 实际上是获取到watcher的value值,即: 'Foo Bar'。
值得注意的是: 当this.fistName或者this.lastName变化时,会计算出一个新的值存到watcher的value,这里的响应式暂不细数。
watch
我们看下精简后的watch相关的代码
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
createWatcher(vm, key, handler)
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
return vm.$watch(expOrFn, handler, options)
}
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
options = options || {}
const watcher = new Watcher(vm, expOrFn, cb, options)
}
class Watcher {
update () {
this.run()
}
run () {
const value = this.get()
if (value !== this.value || isObject(value) || this.deep) {
// set new value
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
}
代码比较简单:
根据监测项expOrFn和回调函数cb创建watcher实例,如果检测项变化,dep会调用对应watcher的update函数,最终执行对应的cb,传递进去value和oldValue参数。
总结
就应用场景而言:
- watch适合监测某个值的变化,在cb回调函数里面完成复杂的逻辑。
- computed适合根据其他的响应式对象获取的一个新值。