1. 核心概念:从内存讲起
要理解拷贝,首先要明白 JavaScript 如何存储值。
-
基本类型(Primitive Types) :
String
,Number
,Boolean
,null
,undefined
,Symbol
,BigInt
。- 这些值直接存储在栈内存 中。变量赋值或传递时,会创建一个值的完全独立的副本。操作一个不会影响另一个。
javascriptlet a = 10; let b = a; // b 是 a 的一个独立副本 b = 20; console.log(a); // 10 (未受影响)
-
引用类型(Reference Types) :
Object
,Array
,Function
,Date
,Map
,Set
等。- 这些值存储在堆内存 中。变量名实际存储的是一个指向堆内存地址的指针(引用)。
- 当你进行
let obj2 = obj1
这样的操作时,你拷贝的只是这个指针 ,而不是堆内存中的对象本身。因此,obj1
和obj2
指向同一个对象。
javascriptlet obj1 = { name: 'Alice' }; let obj2 = obj1; // 只拷贝了引用地址! obj2.name = 'Bob'; console.log(obj1.name); // 'Bob' (原对象被修改了!)
- 正是引用类型的这种特性,使得"拷贝"变得复杂,并衍生出浅拷贝和深拷贝的概念。
2. 浅拷贝 (Shallow Copy)
定义
创建一个新对象,新对象拥有原始对象第一层属性 的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址(即复制引用,而不是复制引用的对象本身)。
简单说:只拷贝了第一层,如果原对象包含嵌套对象,那么嵌套对象仍然是共享的。
前端实现方式
-
展开运算符 (
...
)javascriptconst oldList = [1, 2, 3, { name: 'tom' }] const newList = [...oldList] newList[3].age = 18 console.log('oldList ====>', oldList, 'newList ====>', newList); // [1, 2,3,{ "name": "tom","age": 18},4]
-
Object.assign()
javascriptconst oldList = [1, 2, 3, { name: 'tom' }] const newList = Object.assign([],oldList) newList[3].age = 18 console.log('oldList ====>', oldList, 'newList ====>', newList); // [1, 2,3,{ "name": "tom","age": 18},4]
3. 深拷贝 (Deep Copy)
定义
创建一个新对象,并递归地拷贝原始对象中的所有属性及其嵌套的属性。直到所有层级的所有属性都被拷贝完毕,新对象和原对象完全脱离关系,互不影响。
简单说:从里到外,全部拷贝一份新的。
前端实现方式及优劣
-
JSON.parse(JSON.stringify(object))
(最常用但有缺陷)javascriptconst original = { name: 'Leo', hobbies: ['coding', 'reading'] }; const deepCopy = JSON.parse(JSON.stringify(original));
- 优点:简单粗暴,对于大多数常规数据结构非常有效。
- 致命局限性 :
- 丢失特殊对象 :会丢弃
undefined
、Function
、Symbol
作为值的属性。 - 无法处理循环引用:对象内部或对象之间存在循环引用时会报错。
- 损坏对象类型 :
Date
对象会被转换成 ISO 字符串格式,Set
,Map
,RegExp
等会变成空对象{}
。 - 性能:处理非常大的对象时,序列化和反序列化过程可能较慢。
- 丢失特殊对象 :会丢弃
-
使用第三方库 (
lodash.clonedeep
) (生产环境推荐) -
手动实现递归函数 (面试常见,理解原理)
javascriptfunction deepClone(obj, hash = new WeakMap()) { // 处理基本类型和null/undefined if (obj === null || typeof obj !== 'object') return obj; // 处理Date对象 if (obj instanceof Date) return new Date(obj); // 处理Array对象 if (obj instanceof Array) return obj.map(item => deepClone(item, hash)); // 处理循环引用 if (hash.has(obj)) return hash.get(obj); // 处理普通Object对象 const clonedObj = Object.create(Object.getPrototypeOf(obj)); hash.set(obj, clonedObj); // 缓存克隆对象,用于循环引用 for (let key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key], hash); } } return clonedObj; } const oldList = [1, 2, 3, { name: 'tom' }] const newList = deepClone(oldList) newList[3].age = 18 console.log('oldList ====>', oldList, 'newList ====>', newList);
结果: