数组的响应式
欢迎来到我的博客!
使用 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() - 你所谓的“值”其实是存放在
innerName或obj._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(属性描述符对象)
决定这个属性:
- 值是什么/怎么取值(
value或get) - 能不能改(
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 = 1Object.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 接到“更新视图”的。
赞助支持
如果这篇文章对你有帮助,欢迎赞助支持!
部分内容可能已过时