一维数组存储对象本质是存引用而非对象本身。数组元素保存堆内存中对象的地址,修改属性会影响所有引用该对象的变量,赋值新对象仅改变地址指向,多数组可共享同一对象,需深拷贝实现隔离。

一维数组里存对象,本质是存引用,不是存对象本身。
数组里存的是地址,不是数据
当你写 let arr = [{name: 'Alice'}, {age: 25}],数组每个元素实际保存的,是堆内存中对应对象的地址。栈里只放这些地址,就像通讯录里记的是电话号码,而不是把人搬进本子里。
- 修改
arr[0].name = 'Bob',原对象被改了,所有指向它的变量都会看到变化 - 执行
arr[0] = {name: 'Charlie'},只是把数组第一个槽位换成另一个地址,不影响原来那个对象 -
arr[0] === arr[1]一定为 false,因为两个对象在堆里是不同位置,哪怕内容完全一样
多个数组共享同一对象很常见
一个对象被 push 到多个数组里,不会多占一份内存。比如:
const user = {id: 101};<br>const listA = [user];<br>const listB = [user];
这时 listA[0] 和 listB[0] 指向堆中同一个对象,listA[0] === listB[0] 返回 true。删掉其中一个数组,只要另一个还拿着引用,对象就不会被回收。
想隔离改动?得主动深拷贝
如果希望往数组里塞一份“独立副本”,不能直接赋值,得手动创建新对象:
- 浅层对象:用
{...obj}或Object.assign({}, obj) - 浅层数组:用
[...arr]或arr.slice() - 含嵌套对象:必须用深拷贝,比如
structuredClone(obj)(现代浏览器支持)或JSON.parse(JSON.stringify(obj))(注意函数、undefined、Date 等会丢失)
遍历和修改时要注意引用陷阱
用 forEach 或 map 处理含对象的数组时,容易误以为在操作副本:
-
arr.forEach(item => item.name = 'new')—— 直接改原始对象 -
arr.map(item => ({...item, name: 'new'}))—— 返回新数组,原数组不变 -
arr.filter(item => item.age > 18)—— 安全,只读判断,不改对象
不复杂但容易忽略:数组存对象,省空间也带来联动性,用之前想清楚——要共享,还是各管各的。

















