watch
和computed
是 Vue 中的两个重要的响应式属性,它们在实现机制和使用上存在一些区别。
watch
:用于监听数据的变化,并在数据变化时执行回调函数。可以使用deep
配置项来开启深度监听,监听数据的子属性变化。可以使用immediate
配置项来立即执行回调函数。computed
:用于计算数据,并在数据变化时重新计算数据。可以使用get
函数来计算数据,并在依赖的数据变化时重新计算数据。可以使用set
函数来设置数据,并在数据变化时执行回调函数。
Vue 2 中,使用
Object.defineProperty
来实现数据响应式,并且在数据变化时通过getter
函数来依赖收集和发布订阅。在 Vue 3 中,使用Proxy
来实现数据响应式,并且在数据变化时通过get
函数来依赖收集和发布订阅。这也导致了在源码实现上vue2和vue3的不同。
watch 和 computed
watch用法
watch
用于监听数据的变化,当数据变化时,可以执行相应的操作。它可以用在数据的深层次监听、异步操作、或者需要在数据变化后执行某个操作的场景。
new Vue({
data: {
message: 'hello'
},
watch: {
message(newVal, oldVal) {
console.log('message changed:', newVal, oldVal)
}
}
})
在上面的例子中,我们监听了
message
数据的变化,当message
数据变化时,就会执行console.log
函数。
watch
还有一些高级用法,比如监听对象的变化,可以使用 deep
选项,如下:
new Vue({
data: {
user: {
name: 'John',
age: 27
}
},
watch: {
user: {
handler(newVal, oldVal) {
console.log('user changed:', newVal, oldVal)
},
deep: true
}
}
})
computed用法
computed
用于计算数据,它可以基于数据的变化,动态地计算出新的数据。computed
的值会被缓存,只有当它依赖的数据发生变化时,才会重新计算。
computed
的基本用法如下:
new Vue({
data: {
message: 'hello',
reversedMessage: ''
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
})
在上面的例子中,我们定义了一个 reversedMessage
计算属性,它会动态地计算出 message
的反转字符串。当 message
变化时,reversedMessage
也会随之变化。
computed
还有一些高级用法,比如可以使用 get
和 set
函数来实现双向数据绑定,如下:
new Vue({
data: {
message: 'hello'
},
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(value) {
const names = value.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
})
watch和computed的区别、使用场景
watch
和 computed
都可以用来监听数据的变化,但它们的用途和实现方式有所不同。
watch
是一个监听器,它可以监听数据的变化,并执行相应的操作。watch
可以用在数据的深层次监听、异步操作、或者需要在数据变化后执行某个操作的场景。computed
是一个计算属性,它可以基于数据的变化,动态地计算出新的数据。computed
的值会被缓存,只有当它依赖的数据发生变化时,才会重新计算。computed
可以用在需要对数据进行计算的场景,比如需要对数据进行过滤、排序、或者格式化的场景。computed不支持异步逻辑。
总的来说,
watch
更适用于需要执行某个操作的场景,而computed
更适用于需要对数据进行计算的场景。
面试官问:computed里可以进行异步操作吗?
在Vue中,computed属性默认是同步的,不支持直接进行异步操作。如果需要在computed中进行异步操作,可以使用async/await结合一个异步函数来实现。但是需要注意的是,computed属性应该是一个同步的计算属性,而不应该依赖于异步操作的结果。异步操作应该放在methods中进行处理,或者使用watch监听数据的变化。这样可以确保computed属性的计算是同步的,避免出现意外的行为。
watch源码解析
vue2
Vue 2 可以实现在监视属性的值发生变化时,触发对应的回调函数,并在回调函数中执行相应的操作。主要通过这两个方法
initWatch
和createWatcher实现。initWatch函数遍历
watch
对象的所有属性,如果handler
是一个数组,则遍历数组中的每个元素,并为每个元素创建一个监视器,如果handler
是一个函数或一个纯对象,则直接创建一个监视器。createWatcher
函数用于创建一个监视器,createWatcher
函数最终返回vm.$watch
函数的执行结果,即一个监视器对象。
$watch方法本质上创建一个watcher,监听数据变化,数据变化后触发回调
vue3
watch
函数做了哪些事情:
1.watch
函数会在内部创建一个Watcher
对象,并将其添加到当前组件的watcher
队列中。当响应式数据变化时,会触发Watcher
对象的更新函数,执行回调函数cb
。
watch
函数接收三个参数:
source
:可以是一个响应式数据对象,也可以是一个getter
函数,用于获取需要监听的数据。
cb
:回调函数,在数据变化时执行,可以接收三个参数:newVal
、oldVal
和onInvalidate
。
options
:可选参数,用于配置监听器函数的执行时机和调度方式,包括:
deep
:是否开启深度监听。immediate
:是否立即执行回调函数。flush
:控制回调函数的调度执行时机,可以选择在同步或异步更新后执行。onTrack
:在监听器函数执行前调用。onTrigger
:在监听器函数执行后调用。
Watcher
对象的doWatch函数会执行以下步骤:
在
Watcher
对象的doWatch
函数中,会创建一个effect
函数,并将其与scheduler
函数关联起来。当响应式数据变化时,会触发effect
函数,执行scheduler
函数,从而执行watch
函数中定义的回调函数cb
。
如果
getter
函数执行完成后,需要取消监听,可以调用watch
函数返回的StopWatch
对象的stop
方法,从当前组件的watcher
队列中移除Watcher
对象。
可以看下job或SchedulerJob做了什么
执行
scheduler
函数,从而执行watch
函数中定义的回调函数cb
,并在需要的情况下,更新新值和旧值的值,并执行onInvalidate
函数。
在watch方法里提供了onCleanup方法,可以清除上一次的异步操作结果
computed源码解析
computed的实现原理在面试中经常被问到,如果你只知道维护了一个dirty属性是远远不够的。computed为什么可以是一个属性,依赖的项的变化后,怎么通知computed属性的更改,甚至触发模板的渲染呢?这些都要去源码中获得答案。
vue2
vue2中源码地址:vue-main\src\core\instance\state.ts
vue2源码整体思想如下:
- 计算属性会创建一个计算属性watcher,这个watcher{lazy:true}不会立刻执行
- 通过Object.defineProperty将计算属性定义到实例上
- 当用户取值时会触发getter,拿到计算属性对应的watcher,看dirty是否为true,如果为true则求值。
- 让计算属性watcher中依赖的属性收集最外层的渲染watcher,可以做到依赖的属性变化了,触发计算属性更新。
- 如果依赖的属性没有变化,采用缓存
1.计算属性会创建一个计算属性watcher,这个watcher{lazy:true}不会立刻执行
watcher
的主要作用是:
- 依赖收集:在
watcher
创建时,会将依赖的数据添加到watcher
的dep
属性中,当依赖的数据变化时,会通知watcher
执行回调函数。- 发布订阅:在
watcher
创建时,会将watcher
添加到dep
的subs
属性中,当依赖的数据变化时,会通知dep
执行notify
函数,从而执行watcher
的回调函数。
2.通过Object.defineProperty将方法定义为属性并绑定到实例上,这也是为什么计算属性是个方法却能得到属性
3.computed的核心逻辑:当用户取值时会触发getter,拿到计算属性对应的watcher,看dirty是否为true,如果为true则求值。
并且让计算属性watcher中依赖的属性收集最外层的渲染watcher,可以做到依赖的属性变化了,触发计算属性更新。如果依赖的属性没有变化,采用缓存
vue3
vue3的核心逻辑也是维护一个dirty属性;只不过增加了dirty级别,对dirty进行了分类
与vue2不同的是,vue3依赖收集不依赖watcher,而是改成了effect。
Vue 3 中的
computed
是通过effect
函数和computed
函数实现的。当定义一个computed
计算属性时,Vue 3 会使用effect
函数来创建一个响应式的计算属性对象,并在计算属性的 getter 函数中访问其他响应式数据。这样,计算属性就建立了与依赖数据的关联,当依赖数据变化时,计算属性会重新计算并返回新的值。
-
effect
函数:effect
函数是 Vue 3 中用于创建响应式副作用的函数。它接收一个函数作为参数,并在函数内部访问响应式数据时建立数据与副作用函数之间的依赖关系。当响应式数据发生变化时,effect
函数会重新运行副作用函数,从而触发更新。 -
computed
函数:computed
函数用于创建计算属性。在 Vue 3 中,computed
函数接收一个 getter 函数作为参数,该 getter 函数内部访问其他响应式数据,并返回一个计算值。computed
函数会使用effect
函数来创建一个响应式的计算属性,当依赖的响应式数据发生变化时,计算属性会重新计算并返回新的值。