JavaScript中判断变量是否引用同一对象及扩展运算符拷贝疑问
我是JavaScript新手,正在学习扩展运算符(spread operator)。之前了解到它对数组或对象的顶层元素是深拷贝,嵌套数组或对象是浅拷贝,于是写了测试代码,但实际输出和我想的不一样:copiedObject.name === originalObject.name输出true,而我预期是false。另外还有两个问题:
- 怎么可视化代码里的引用逻辑?
- 怎么在JavaScript里判断两个属性是否引用同一个对象?
测试代码
const originalObject = { name: 'Alice', address: { street: '123 Main St', city: 'Anytown', }, }; const copiedObject = { ...originalObject }; console.log(copiedObject.name === originalObject.name); // 预期输出false,实际输出true originalObject.address.city = 'Newtown'; console.log(copiedObject.address.city); // 输出"Newtown"(修改影响了两个对象) console.log(originalObject.address.city); console.log(copiedObject.address === originalObject.address); // 输出true(是不是说明两者引用同一个对象?)
问题解答
1. 为什么copiedObject.name === originalObject.name输出true?
你对扩展运算符的拷贝逻辑理解有个小偏差:扩展运算符对顶层对象是创建新对象(深拷贝顶层容器),但顶层属性如果是原始值(字符串、数字、布尔、null、undefined、Symbol),是直接复制值,而不是创建新的引用。
字符串"Alice"是原始值,原始值在JavaScript里是存在栈内存中的,没有引用地址一说。copiedObject.name和originalObject.name都是值为"Alice"的原始值,===比较原始值时是看内容是否完全相同,所以结果是true。
而像address这种嵌套对象是引用类型,扩展运算符只是把原对象里address的引用复制给了新对象,所以两个对象的address指向同一个堆内存里的对象,修改其中一个的city,另一个也会跟着变。
2. 怎么判断两个属性是否引用同一个对象?
直接用===(推荐用===而非==)比较就行。对于引用类型(对象、数组、函数等),===比较的是它们在内存中的地址是否相同——如果返回true,就说明两个属性引用的是同一个对象,比如你代码里的copiedObject.address === originalObject.address返回true,就证明这俩指向同一个address对象。
3. 如何可视化代码逻辑?
- 浏览器调试工具:用Chrome/Firefox的开发者工具,在代码里加断点,调试时查看变量的引用关系;也可以用内存快照功能,查看对象的引用链。
- 手绘草图:自己画栈和堆的结构:栈里存变量和原始值,堆里存引用类型的对象。比如
originalObject和copiedObject是栈里的两个变量,各自指向堆里的新对象;这两个新对象的name属性都对应栈里的"Alice";而它们的address属性都指向堆里同一个address对象。 - 可视化工具:用本地或在线的JS可视化工具(这类工具能把代码转换成图形化的引用关系图,直观展示哪些变量指向同一个对象)。
内容的提问来源于stack exchange,提问作者Oc Oc

