1556 字
8 分钟

响应式对象的赋值

欢迎来到我的博客!#

使用 Markdown 格式编写。

今天学到了什么#

  • 响应式对象的赋值

为什么,响应式对象里面的每个属性不都是响应式的吗?难道说关键点在于get返回的是什么,基本类型返回的是值,对象返回的是地址?#

对,关键点基本就在你说的这两句话上:Vue 的响应式不是“值本身带响应式”,而是“对某个对象的某个属性的读写被拦截并追踪”。所以“属性是响应式的”这句话更准确的理解是:person.age 这次访问/修改是可追踪的,而不是“age 这个数字离开对象后还自带响应式”。

更具体拆开看:

  • reactive() 返回的是一个 Proxy。
  • 响应式依赖收集发生在“某个 effect(渲染/计算属性/侦听器)里执行到 get 时”,Vue 记录:谁在用 target 的哪个 key
  • 触发更新发生在“对同一个 target/keyset 时”。

然后你遇到的现象来自 get 的返回值类型:

1) 基本类型:get 返回“值”#

let age = person.age // age 得到的是一个 number 值(比如 18)
age++ // 改的是局部变量 age

这里后续根本没有再发生对 person.ageset,所以 Vue 没有任何可触发的点;局部变量也不在 Proxy 管控范围内。

2) 对象类型:get 通常返回“代理对象(引用)”#

const info = person.info
info.name = 'x'

person.info 通常会被 Vue 深度代理(返回一个 proxy),所以你后面改 info.name 本质仍然是在改某个 proxy 的属性 → 能触发依赖更新。(前提是模板/计算等确实读取过对应属性)

所以你总结的这句很到位:

  • 基本类型返回的是值(拷贝出来就断开)
  • 对象返回的是引用(更准确:返回的是“代理对象引用”),所以还能沿着 proxy 继续追踪

一个容易误会的补充#

就算是对象引用,这种操作也不会触发更新:

let info = person.info
info = { name: 'y' } // 只是把局部变量 info 指向新对象

因为这没有对 person.infoset,Vue 追踪不到。

该怎么“把属性拿出来还保持联动”#

toRef/toRefs,它不是把值拷贝出来,而是做了一个“指向原属性的 ref”:

const age = toRef(person, 'age')
age.value++ // 等价于 person.age++

因为这没有对 person.info 做 set?不对吧,响应式对象里面每一个属性都是响应式的,就都有对应的get和set,虽然赋值给info了,但是info指向的是具有响应式的对象,info被赋值不应该触发set吗1#

你这里卡点非常典型:Proxy 的 get/set 只会拦截“对象属性的读写”,拦截不到“变量本身的重新赋值”

1) “info 被赋值”触发的是谁的 set?#

看两种写法:

const info = person.info
info = { name: 'y' }

这句 info = ... 是在给“局部变量 info”重新绑定一个新值。
它不是“对某个对象的某个属性赋值”,所以不会走任何 Proxy 的 set

只有这种才会触发响应式的 set

person.info = { name: 'y' } // ✅ 这是给 person 的 info 属性赋值,会触发 Proxy.set

2) “info 指向的是响应式对象”≠“info 这个变量是响应式的”#

const info = person.info 的结果通常是:info 拿到一个 Proxy 对象的引用(或者是已被代理的对象引用)。

  • 你改它的属性:info.name = 'x'
    这属于“对 proxy 对象的属性 set”,所以会触发更新(前提:模板/effect 读取过 name)。

  • 你把变量改成指向别的对象:info = {...}
    这属于“改变量绑定”,Vue 没有任何机制能拦截普通 JS 变量赋值。

Vue 响应式系统追踪的是:target + key(哪个对象的哪个属性),而不是追踪“某个局部变量现在指向谁”。

3) 为什么 Vue 不把局部变量也做成响应式?#

因为 JS 语言层面没有“拦截变量赋值”的通用机制(Proxy 只能代理对象)。
所以 Vue 才提供 ref:让你把“变量”包进一个对象属性里,用 .value 这种“属性 set”来实现可追踪:

const infoRef = ref(person.info)
infoRef.value = { name: 'y' } // ✅ 这是对 infoRef 这个对象的 value 属性 set,可触发

一句话总结#

  • person.info = ...:改的是响应式对象的属性 → 触发 set
  • info = ...:改的是局部变量绑定 → 不会触发任何响应式

person.info = { name: ‘y’ }能被拦截到,是因为info本身就是person里面的属性,而赋值给info后,info本身就只是一个变量,所以没有谁能拦截他的指向#

对,你这句话已经抓到本质了,而且表述很准确。

更精确地说:

  • person.info = { name: 'y' }
    这是一次“对象属性赋值”。person 是响应式代理(Proxy),所以这一步会走 Proxy 的 set(target, key, value),Vue 就能拦截、触发更新。

  • const info = person.info; info = { name: 'y' }
    第一行是“读取对象属性”(会走 get),第二行是“给普通变量重新赋值”。变量赋值不是“某个对象的某个属性 set”,所以 没有 Proxy 介入点,自然也谈不上拦截、触发更新。

你可以把它理解成两类“可被 Vue 追踪的写操作”:

  1. 写到“响应式对象的属性”上:proxy.xxx = ...(能拦截)
  2. 写到“ref 的 value 属性”上:someRef.value = ...(能拦截)

而“写到普通变量”上:x = ...(不能拦截)

所以 Vue 才会让你用 ref 来把“变量”也变成可追踪对象:变量的“指向变化”就被转化成了 ref.value 这个属性的变化。

如果你还想再加深一点:Vue 的依赖关系记录的是“哪个 effect 依赖了哪个 target/key”。
person.info 有明确的 target=personRaw, key='info'
而局部变量 info 没有 target/key 这种可追踪的落点,所以系统无法建立依赖图。

Footnotes#

  1. 这两句话完全是两码事

赞助支持

如果这篇文章对你有帮助,欢迎赞助支持!

赞助
最后更新于 2026-04-18,距今已过 136 天

部分内容可能已过时

目录