对象引用和复制
JavaScript 中对象(Object)与其他简单数据类型的不同点在于对象在内存中是通过“引用”拿到存储的数据的,而其他简单数据类型,例如string和number在内存中的数据是“一体的”的。
例如下面有两个数据
js1let a = 1 2let b = { name: 'qyx' }
在内存中,它们的存储方式类似这样:

所谓的引用,实际上是该对象在内存中的地址,也就是说赋值的b 变量保存着数据在内存中的地址,JavaScript 通过这个地址来获取内存中的实际数据。
这样做的结果是拷贝对象和拷贝简单数据类型的逻辑是不一样的。
当我们拷贝简单数据类型时:
js1let a = 1 2let cloneA = a
内存中就会出现两个数据。

当我们用同样的方法拷贝复杂数据类型时:
js1let b = { name: 'qyx' } 2let cloneB = b
仅仅拷贝了 b 对象中保存的地址,所以最终内存中有两个变量名引用了同一个数据

当我们用其中一个变量修改数据时,另一个变量能访问到被修改后的数据。
js1let b = { name: 'qyx' } 2let cloneB = b 3b.name = 'qiuyanxi' 4cloneB.name // "qiuyanxi"
对象之间的比较
只有对象保存的地址是一样的,对象之间的相等比较才会相等。
js1let a = { name: 'qiuyanxi' } 2let b = a 3a === b // true
如果保存地址不一样,即使看起来两个数据是一样的,相等比较也会返回 false
js1let a = { name: 'qiuyanxi' } 2let b = { name: 'qiuyanxi' } 3a === b // false
对象浅拷贝的方式
通常我们会使用let clone= {...user}语法来浅拷贝对象。
还有一个很简单的 API,能够“浅”拷贝对象,不过通常我们用它来合并多个对象的属性——Object.assign
js1Object.assign(dest, [src1, src2, src3...])
第一个属性 dest 是目标对象,后面的 src 则是多个对象
js1let dest = {} 2Object.assign(dest, { name: 'qyx' }, { age: 30 }) 3dest // {name: 'qyx', age: 30}
如果属性名已经存在,那么会默认覆盖:
js1let user = { name: 'John' } 2 3Object.assign(user, { name: 'Pete' }) 4 5alert(user.name) // 现在 user = { name: "Pete" }
除此之外,还有很多方法可以做到。
对象深拷贝的方式
对象中的属性还可以保存对象——实际上是其他对象的引用。
js1let user = { 2 name: 'John', 3 sizes: { 4 height: 182, 5 width: 50, 6 }, 7} 8 9alert(user.sizes.height) // 182
现在这样拷贝 clone.sizes = user.sizes 已经不足够了,因为 user.sizes 是个对象,它会以引用形式被拷贝。因此 clone 和 user 会共用一个 sizes:
js1let user = { 2 name: 'John', 3 sizes: { 4 height: 182, 5 width: 50, 6 }, 7} 8 9let clone = Object.assign({}, user) 10 11alert(user.sizes === clone.sizes) // true,同一个对象 12 13// user 和 clone 分享同一个 sizes 14user.sizes.width++ // 通过其中一个改变属性值 15alert(clone.sizes.width) // 51,能从另外一个获取到变更后的结果
为了解决这个问题,我们需要用到深拷贝。我们应该使用一个拷贝循环来检查 user[key] 的每个值,如果它是一个对象,那么也复制它的结构。这就是所谓的“深拷贝”。
最终我们会让 user 和 clone 成为两个真正独立的对象。
-
最简单的深拷贝
JSON.stringify和JSON.parse,不过这种方法局限性很多,比如不能拷贝函数。js1const obj = { 2 name: 'yanxi', 3 props: { name: 'qiu' }, 4} 5const obj2 = JSON.parse(JSON.stringify(obj)) 6obj2.props.name = '11111' 7obj 8//{name: "yanxi", props: {name: "qiu"}} -
最方便的深拷贝
-
自己实现的深拷贝
总结
对象通过引用被赋值和拷贝。
一个变量存储的不是“对象的值”,而是一个对值的“引用”(内存地址)。因此,拷贝此类变量或将其作为函数参数传递时,所拷贝的是引用,而不是对象的数据本身。
所有通过被拷贝的引用的操作(如添加、删除属性)都作用在同一个对象上。
为了创建“真正的拷贝”(一个克隆),我们可以使用 Object.assign 来做所谓的“浅拷贝”(嵌套对象被通过引用进行拷贝)或者使用“深拷贝”函数,例如 _.cloneDeep(obj)。