一文搞懂 JavaScript 中的 copy:值拷贝、浅拷贝与深拷贝
刚学 JS 的时候,我经常遇到一个问题:明明拷贝了一个对象,结果改了新对象,原对象也跟着变了?
后来我才发现,这背后其实是 JavaScript 中“拷贝机制”没理解清楚。
这篇文章是我学习 JS copy(拷贝)过程中的一次系统整理,希望能帮到和我一样的 JS 新手。
一、为什么 JS 的 copy 这么容易出问题?
先看一段代码:
const obj1 = { name: 'Tom', like: { sport: 'football' } }
const obj2 = obj1
obj2.like.sport = 'basketball'
console.log(obj1.like.sport) // basketball
很多新手都会疑惑:我只是改了 obj2,为什么 obj1 也变了?
原因只有一个:obj1 和 obj2 指向的是同一个对象引用。
要理解 copy,必须先理解 JS 中的数据类型。
二、值类型 vs 引用类型
2.1 值类型(基本数据类型)
- Number
- String
- Boolean
- undefined
- null
- Symbol
- BigInt
特点:
- 拷贝的是「值本身」
- 修改互不影响
let a = 10
let b = a
b = 20
console.log(a) // 10
2.2 引用类型
- Object
- Array
- Function
- Date、Map、Set 等
特点:
- 变量中存的是“地址”
- 多个变量可能指向同一块内存
let obj1 = { a: 1 }
let obj2 = obj1
obj2.a = 2
console.log(obj1.a) // 2
所以,拷贝对象时,如果只是拷贝引用,就会产生“联动修改”的问题。
三、浅拷贝
什么是浅拷贝?
只拷贝对象的第一层
如果属性值还是对象,那么拷贝的仍然是引用。
常见的浅拷贝方式
数组
arr.slice()
[...arr]
arr.concat()
对象
Object.assign({}, obj)
{ ...obj }
手写一个浅拷贝
function shallowCopy(obj) {
let result = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key]
}
}
return result
}
测试一下:
const obj = {
name: '玉米',
like: {
sport: 'football'
}
}
const newObj = shallowCopy(obj)
newObj.like.sport = 'basketball'
console.log(obj.like.sport) // basketball ❌
问题依然存在:内部对象还是共享引用
四、深拷贝
什么是深拷贝?
递归地拷贝所有层级
新对象和原对象完全独立,互不影响。
五、常见的深拷贝方案对比
5.1 structuredClone(推荐)
const newObj = structuredClone(obj)
优点
- 原生 API
- 支持 Object、Array、Map、Set、Date 等
- 能处理循环引用
缺点
- 不支持函数
- 旧浏览器不支持
5.2 JSON.parse(JSON.stringify())
const newObj = JSON.parse(JSON.stringify(obj))
优点
- 写法简单
- 兼容性好
缺点(非常重要)
- 丢失 function
- 丢失 undefined、Symbol、BigInt
- NaN / Infinity 会被处理成 null
- 无法处理循环引用
只能用于结构简单、数据纯净的对象
5.3 手写递归深拷贝
function deepClone(obj, map = new WeakMap()) {
if (obj === null) return null
if (typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj)
const result = Array.isArray(obj) ? [] : {}
map.set(obj, result)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key], map)
}
}
return result
}
支持循环引用
未处理 Map / Set / Date / RegExp / Symbol 等
适合学习原理,不建议直接用于生产
六、深拷贝中最容易踩的坑
- 函数无法被 JSON 或 structuredClone 拷贝
- 循环引用会让 JSON.stringify 报错
- Symbol / BigInt / undefined 会丢失
- 原型链、不可枚举属性不会被普通拷贝方式处理
所以: 不要盲目深拷贝,先想清楚你到底需不需要
最后
理解 copy,不只是为了面试,更是为了写出不踩坑的代码。
这篇文章是我作为 JS 新手的学习总结,如果有理解不准确的地方,欢迎在评论区指出。如果你也被 copy 问题坑过,欢迎留言交流~