1516 字
8 分钟

数组的响应式

欢迎来到我的博客!#

使用 Markdown 格式编写。

今天学到了什么#

  • 数组的响应式
  • get和set何时被调用
  • Object.defineProperty详解

数组的响应式#

1)给整个数组变量重新赋值:任何赋值都能响应

this.users = users

这里改的是 data 里的 users 这个属性本身(从旧数组换成新数组),Vue2 对 users 有 setter,所以 一定能检测到,并触发更新。
这不属于“数组变异方法监听”的范畴,而是普通的“对象属性 setter”。

2)不换数组,只在原数组上增删改:才依赖那几个方法 比如你不换引用,而是改同一个数组:

  • ✅ 能被 Vue2 监听(会更新):
    • push/pop/shift/unshift/splice/sort/reverse
  • ⚠️ Vue2 监听不到或不稳定(不会触发视图更新):
    • this.users[0] = xxx(按下标替换)
    • this.users.length = 0(改长度)

所以“只有那几个数组方法才能被检测到”的完整意思是:

当你对 同一个数组实例 做“内部变动”时,Vue2 只能可靠拦截那几个变异方法。

而你前面这种:

把接口返回的整个数组赋给 data 里的数组属性

属于第 1 类(换引用),不需要用那几个方法。

一句话总结

  • 换数组:this.users = newArr ✅ always 响应
  • 改数组内部:用 splice/push/... ✅ 响应;别用 arr[index]=...

如果你告诉我你实际想做的是“追加一批用户”还是“替换某一项用户”,我可以直接给你对应最推荐的写法。

那get和set何时被调用呢?#

只要 name 这个属性是用 getter/setter(访问器属性) 定义出来的,那么:

1) 读 obj.name 会自动调用 get#

let a = obj.name

这里的 obj.name 不是“直接取字段”,而是 触发 getter

  • JS 引擎会自动调用你定义的 get()
  • get() 的返回值,就是 obj.name 这次读取的结果
  • 所以 a 会拿到 get() 返回的值

2) 写 obj.name = 'abc' 会自动调用 set#

obj.name = 'abc'

这会 触发 setter

  • JS 引擎会自动调用你定义的 set('abc')
  • 你可以在 set 里做“校验、转换、通知更新”等一系列处理
  • 注意:如果你在 set 里没有把值保存到某个地方(比如内部变量/另一个字段),那之后 get 可能读不到你想要的值

3) “创建 name 的时候就赋值”是什么意思?#

这里要区分两种定义方式:

A. 普通属性(数据属性)#

obj.name = 'tom'

这是普通数据属性,没有 get/set,读写就是直接取值/改值。

B. 访问器属性(有 get/set)#

访问器属性本身不存 value,它只是一对函数入口。 所以你不能这样写:

Object.defineProperty(obj, 'name', {
value: 'tom', // ❌ 和 get/set 不能共存
get(){}, set(){}
})

如果你想“定义时就有初始值”,正确方式是把值存到别的地方,例如闭包变量或 _name

闭包变量版(最直观)

let innerName = 'tom'
Object.defineProperty(obj, 'name', {
get() { return innerName },
set(v) { innerName = v }
})

用另一个字段存(Vue2 常见)

obj._name = 'tom'
Object.defineProperty(obj, 'name', {
get() { return this._name },
set(v) { this._name = v }
})

4) “name 的值依然要靠 get 才能得到吗?”#

如果 name 是访问器属性(有 get/set):

  • 是的,任何读取 obj.name 的动作都会走 get()
  • 你所谓的“值”其实是存放在 innerNameobj._name 之类的位置
  • get() 只是负责把那个“真实存储的值”返回出来(或者返回一个计算结果)

一句话:

  • 定义了 getter/setter 后,obj.name 就变成“函数调用的语法糖”:读=调 get,写=调 set。
  • 初始值要存到别处(闭包/_name),否则 getter 没东西可返回。

Object.defineProperty详解#

Object.defineProperty 就 3 个参数,每个参数干什么、第三个参数里每个字段是什么意思,搞清楚就能读懂 Vue2 响应式源码。

函数签名(3 个参数)#

Object.defineProperty(target, propertyKey, descriptor)

参数 1:target#

  • 要被“定义属性”的那个对象
  • 例:const obj = {}; Object.defineProperty(obj, ...)

参数 2:propertyKey#

  • 属性名(字符串或 Symbol)
  • 例:'name'Symbol('id')

参数 3:descriptor(属性描述符对象)#

决定这个属性:

  • 值是什么/怎么取值(valueget
  • 能不能改(writable / 有没有 set
  • 能不能遍历出来(enumerable
  • 能不能再次被 redefine / 能不能 delete(configurable

descriptor 分两大类(重点)#

descriptor 只能二选一

A. 数据属性(data descriptor)#

value + writable 描述“存储值的属性”:

Object.defineProperty(obj, 'a', {
value: 1,
writable: true,
enumerable: true,
configurable: true
})

字段含义:

  • value:属性值
  • writable:能否 obj.a = ... 改值
  • enumerable:能否被 Object.keys / for...in 遍历出来
  • configurable:能否 delete obj.a、能否再次 defineProperty 改描述符

B. 访问器属性(accessor descriptor)#

get + set 描述“读写时执行函数”的属性(Vue2 用这个):

let inner = 1
Object.defineProperty(obj, 'a', {
get() { return inner },
set(v) { inner = v },
enumerable: true,
configurable: true
})

字段含义:

  • get():读取 obj.a 时调用,返回值就是 obj.a 的结果
  • set(v):给 obj.a = v 赋值时调用
  • enumerable / configurable:同上

注意:value/writable 不能和 get/set 同时存在,否则会报错。


默认值(很多人踩坑)#

如果你没写:

  • enumerable 默认 false(遍历不到)
  • configurable 默认 false(不能 delete、也不能再改描述符)
  • 数据属性的 writable 默认 false(不能改) 所以很多时候你 define 完发现:
  • Object.keys(obj) 没它 → 因为没设 enumerable:true
  • 想再 define 一次改规则失败 → 因为 configurable:false

返回值 & 作用#

  • 返回值:返回 target(也就是第一个参数对象)
  • 作用:在对象上“精确定义/改造”一个属性

和 Vue2 响应式怎么对应(你现在学的核心)#

Vue2 会对 data 里的每个 key 用 defineProperty 包一层:

  • get:谁读取了这个值,就把“谁”记录下来(依赖收集)
  • set:值一变,通知所有依赖它的地方更新(派发更新)

如果你把你截图里那段 Vue2 源码(或你笔记里的 defineReactive 那段)贴出来,我可以按它的每个参数、每一行逻辑逐句讲清楚它是怎么把 get/set 接到“更新视图”的。

赞助支持

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

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

部分内容可能已过时

目录