0

0

Vue3侦听器的实现原理是什么

王林

王林

发布时间:2023-05-16 15:04:06

|

1634人浏览过

|

来源于亿速云

转载

侦听响应式对象

前面我们聊到计算属性,它可以自动计算并缓存响应式数据的值。而如果我们仅需要在响应式数据变化时,执行一些预设的操作,就可以使用watch侦听器。我们还是先来实现一个最简单的例子,然后来一点一点扩充它。

const data = {foo: 1}
const obj = reactive(data)
watch(obj, () => {
  console.log('obj已改变')
})

在这个例子中,我们使用了watch侦听器,当obj的属性被改变时,控制台应该会打印出obj已改变。基于前面我们对计算属性的实现,这里我们已经有了一个大概的思路。把watch视为响应式对象的副作用函数,当响应式对象改变时,触发执行该副作用函数。

想要触发副作用函数,必须先收集它,还记得副作用函数是如何收集的吗?对,当响应式数据被get时,收集副作用函数。所以首先,我们需要让watch被响应式对象收集到。     

function watch(getter, cb) {
  effect(
    () => getter.foo
  )
}

接着,我们还需要让我们预设的方法被执行。当响应式数据被set时,触发副作用函数。这里我们想触发的是cb这个传入的回调函数,这里我们就又能用到实现计算属性时的调度器了,当调度器存在时,set触发的trigger会先执行调度器中的函数。

function watch(getter, cb) {
  effect(
    () => getter.foo,
    {
      scheduler() {
        cb()
      }
    }
  )
}

Vue3侦听器的实现原理是什么

立即学习前端免费学习笔记(深入)”;

一个简单的侦听器已经完成了!这里我们为了简单,把功能写死了,仅支持对obj.foo的侦听。接下来,我们就要想想,如何实现对响应式对象的任意属性进行侦听?

按照前面的思路,想要实现对响应式对象的任意属性的侦听,就需要我们get到该对象的每一个属性,这就需要我们对响应式对象进行一次递归遍历。

function traverse(value, seen = new Set()) { // (1)
  if(typeof value !== 'object' || value === null || seen.has(value)) return
  seen.add(value)
  for(const key in value) {
    traverse(value[key], seen)
  }
  return value
}

为了避免递归遍历对象时,循环引用造成的死循环,我们在(1)处创建了Set,当重复出现相同的对象时,直接返回。

侦听属性值

在Vue3中,我们不能直接侦听响应式对象的属性值。如果需要侦听响应式对象的属性值,就需要一个getter函数,让侦听器能被响应式对象收集到。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  () => {
  console.log('obj.foo已改变')
})

指定了属性就意味着,当前的侦听器仅会被指定的属性触发,就无需递归遍历整个响应式对象了。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter) // (2)
  effect(
    () => getter(),
    {
      scheduler() {
        cb()
      }
    }
  )
}

Vue3侦听器的实现原理是什么

在(2)处,我们增加了一个判断,如果传入的已经是getter函数,我们直接使用,如果不是getter函数,则认为是一个响应式对象,就需要进行递归遍历。

侦听获取新值和旧值

在Vue中我们还需要能够在回调函数cb()中拿到响应式数据更新前后的新值与旧值。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

接下来的问题是,如何获取newValueoldValuenewValue好解决,执行完回调函数cb()得到的就是newValue,但这里如何获取oldValue的值呢?要从watch中拿到旧值,那就不能让副作用函数被立即执行。这里想到了什么?对,在实现计算属性的时候,我们用到过的lazy,它可以禁止副作用函数自动执行。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue
  const effectFn = effect(
    () => getter(),
    {
      lazy: true, // (3)
      scheduler() {
          cb(oldValue)
      }
    }
  )
  oldValue = effectFn() // (4)
}

在(3)处我们设置了lazy开关,设置了lazy后,副作用函数的执行权就交到了我们自己手上。在(4)处,我们手动执行了副作用函数。这里可以需要我们向前回顾一下,前面我们传入的getter是一个函数() => obj.foo,而effect函数的第一个参数就是真正被执行的副作用函数,所以我们手动执行的,其实就是函数() => obj.foo,这样我们就拿到了旧值。

如何获取新值呢?在响应式数据的值更新后,副作用函数effect会被触发执行,当调度器属性存在时,执行调度器。在调度器中,我们可以再次执行副作用函数,通过() => obj.foo拿到改变后的新值。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue, newValue
  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler() {
        newValue = effectFn()
        cb(newValue, oldValue)
        oldValue = newValue // (5)
      }
    }
  )
  oldValue = effectFn()
}

在(5)处,执行完回调函数cb(),我们进行了一下善后工作,更新了oldValue的值,为下一次回调做准备。

Vue3侦听器的实现原理是什么

有时,我们还希望侦听器可以在创建时就立即执行回调函数。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
      console.log('newValue:', newValue,', oldValue:', oldValue)
  },
  { immediate: true }
)

immediate的值为true时,需要立即执行。明确了需求,我们来完善watch侦听器。

function watch(getter, cb, options = {}) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue, newValue
  function job() { // (6)
    newValue = effectFn()
    cb(newValue, oldValue)
    oldValue = newValue
  }

  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler: job,
    }
  )

  if(options.immediate) {  // (7)
    job()
  } else {
    oldValue = effectFn()
  } 
}

在(6)处,我们抽离了回调函数的执行逻辑,当options.immediate存在时,直接触发执行。

Vue3侦听器的实现原理是什么

实现效果

Vue3侦听器的实现原理是什么

相关专题

更多
高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

4

2026.01.16

全民K歌得高分教程大全
全民K歌得高分教程大全

本专题整合了全民K歌得高分技巧汇总,阅读专题下面的文章了解更多详细内容。

3

2026.01.16

C++ 单元测试与代码质量保障
C++ 单元测试与代码质量保障

本专题系统讲解 C++ 在单元测试与代码质量保障方面的实战方法,包括测试驱动开发理念、Google Test/Google Mock 的使用、测试用例设计、边界条件验证、持续集成中的自动化测试流程,以及常见代码质量问题的发现与修复。通过工程化示例,帮助开发者建立 可测试、可维护、高质量的 C++ 项目体系。

10

2026.01.16

java数据库连接教程大全
java数据库连接教程大全

本专题整合了java数据库连接相关教程,阅读专题下面的文章了解更多详细内容。

33

2026.01.15

Java音频处理教程汇总
Java音频处理教程汇总

本专题整合了java音频处理教程大全,阅读专题下面的文章了解更多详细内容。

15

2026.01.15

windows查看wifi密码教程大全
windows查看wifi密码教程大全

本专题整合了windows查看wifi密码教程大全,阅读专题下面的文章了解更多详细内容。

42

2026.01.15

浏览器缓存清理方法汇总
浏览器缓存清理方法汇总

本专题整合了浏览器缓存清理教程汇总,阅读专题下面的文章了解更多详细内容。

7

2026.01.15

ps图片相关教程汇总
ps图片相关教程汇总

本专题整合了ps图片设置相关教程合集,阅读专题下面的文章了解更多详细内容。

9

2026.01.15

ppt一键生成相关合集
ppt一键生成相关合集

本专题整合了ppt一键生成相关教程汇总,阅读专题下面的的文章了解更多详细内容。

6

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.4万人学习

Vue3.x 核心篇--十天技能课堂
Vue3.x 核心篇--十天技能课堂

共30课时 | 1.5万人学习

Vue3.x新特性篇--十天基础课堂
Vue3.x新特性篇--十天基础课堂

共20课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号