vue源码解析—— watch/computed的实现逻辑和区别

发布于:2024-03-29 ⋅ 阅读:(24) ⋅ 点赞:(0)

watchcomputed 是 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 还有一些高级用法,比如可以使用 getset 函数来实现双向数据绑定,如下:

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的区别、使用场景

watchcomputed 都可以用来监听数据的变化,但它们的用途和实现方式有所不同。

  • 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回调函数,在数据变化时执行,可以接收三个参数:newValoldValonInvalidate

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源码整体思想如下:

  1. 计算属性会创建一个计算属性watcher,这个watcher{lazy:true}不会立刻执行
  2. 通过Object.defineProperty将计算属性定义到实例上
  3. 当用户取值时会触发getter,拿到计算属性对应的watcher,看dirty是否为true,如果为true则求值。
  4. 让计算属性watcher中依赖的属性收集最外层的渲染watcher,可以做到依赖的属性变化了,触发计算属性更新。
  5. 如果依赖的属性没有变化,采用缓存

 1.计算属性会创建一个计算属性watcher,这个watcher{lazy:true}不会立刻执行

watcher 的主要作用是:

  • 依赖收集:在 watcher 创建时,会将依赖的数据添加到 watcherdep 属性中,当依赖的数据变化时,会通知 watcher 执行回调函数。
  • 发布订阅:在 watcher 创建时,会将 watcher 添加到 depsubs 属性中,当依赖的数据变化时,会通知 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 函数中访问其他响应式数据。这样,计算属性就建立了与依赖数据的关联,当依赖数据变化时,计算属性会重新计算并返回新的值。

  1. effect 函数effect 函数是 Vue 3 中用于创建响应式副作用的函数。它接收一个函数作为参数,并在函数内部访问响应式数据时建立数据与副作用函数之间的依赖关系。当响应式数据发生变化时,effect 函数会重新运行副作用函数,从而触发更新。

  2. computed 函数computed 函数用于创建计算属性。在 Vue 3 中,computed 函数接收一个 getter 函数作为参数,该 getter 函数内部访问其他响应式数据,并返回一个计算值。computed 函数会使用 effect 函数来创建一个响应式的计算属性,当依赖的响应式数据发生变化时,计算属性会重新计算并返回新的值。

本文含有隐藏内容,请 开通VIP 后查看