JS深\浅拷贝
Breezli深/浅拷贝
深浅拷贝的核心区别在于是否递归复制对象的所有层级引用类型属性,常见实现方案如下:
浅拷贝实现
将原始对象或数组中的元素逐个复制到新对象或数组中
只会复制它们的引用,而不会创建这些嵌套对象或数组的新副本(改变原始对象会同时影响拷贝对象)
Object.assign()
1 2 3 4 5
| const obj = { a: 1, b: { c: 2 } }; const copy = Object.assign({}, obj); console.log(copy); obj.b.c = 3; console.log(copy.b.c);
|
扩展运算符(...
)
1 2 3 4 5
| const obj = { a: 1, b: { c: 2 } }; const copy = { ...obj }; console.log(copy); obj.b.c = 3; console.log(copy.b.c);
|
数组方法
slice
、concat
等返回新数组,但嵌套对象仍共享引用:
1 2 3 4 5
| const arr = [1, { x: 2 }]; const copy = arr.slice(); console.log(copy); arr[1].x = 3; console.log(copy[1].x);
|
深拷贝实现
递归地复制整个对象及其嵌套的对象或数组,创建一个完全独立的新对象或数组
1. JSON序列化(局限性明显)
1
| const deepCopy = JSON.parse(JSON.stringify(obj));
|
1 2 3 4 5
| const obj = { a: 1, b: { c: 2 } }; const deepCopy = JSON.parse(JSON.stringify(obj)); console.log(deepCopy); obj.b.c = 3; console.log(deepCopy.b.c);
|
缺点:
- 忽略
undefined
、Symbol
、函数属性
- 无法处理循环引用(如
obj.self = obj
)
- 破坏特殊对象类型(如
Date
转为字符串,RegExp
转为空对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const obj = { name: "example", age: undefined, today: new Date(), pattern: /abc/g, sayHello: function() { console.log("Hello"); } }; obj.self = obj;
const copiedObj = JSON.parse(JSON.stringify(obj)); console.log(copiedObj); console.log(typeof copiedObj.today); console.log(copiedObj.pattern);
|
JSON.stringify()
只会序列化对象的可枚举属性,任何不可枚举的属性都会被忽略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const obj = Object.create({}, { hidden: { value: "This is hidden", enumerable: false }, visible: { value: "This is visible", enumerable: true } });
const copiedObj = JSON.parse(JSON.stringify(obj)); console.log(copiedObj.hidden); console.log(copiedObj.visible);
|
JSON.stringify()
只会序列化对象自身的属性,不会保留原型链上的属性或方法
1 2 3 4 5 6 7 8 9 10 11 12
| function Person(name) { this.name = name; }
Person.prototype.sayHello = function() { console.log("Hello, my name is " + this.name); };
const person = new Person("Alice");
const copiedPerson = JSON.parse(JSON.stringify(person)); console.log(copiedPerson);
|
2. 递归手动实现
在这里放一个对象,以便更好地理解
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = { name: "root", child: { name: "child", sibling: { name: "sibling" } } }; obj.child.sibling.parent = obj;
const clonedObj = deepClone(obj);
|
基础版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function deepClone(target, map = new WeakMap()) { if (typeof target !== 'object' || target === null) return target; if (map.has(target)) return map.get(target);
const cloneTarget = Array.isArray(target) ? [] : {}; map.set(target, cloneTarget);
for (const key in target) { if (target.hasOwnProperty(key)) { cloneTarget[key] = deepClone(target[key], map); } } return cloneTarget; }
|
优化版本:处理特殊对象类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const getType = obj => Object.prototype.toString.call(obj).slice(8, -1);
function deepClone(target, map = new WeakMap()) { if (typeof target !== 'object' || target === null) return target; if (map.has(target)) return map.get(target); const type = getType(target); let clone; switch(type) { case 'Date': return new Date(target.getTime()); case 'RegExp': return new RegExp(target.source, target.flags); case 'Set': clone = new Set(); target.forEach(v => clone.add(deepClone(v, map))); return clone; case 'Map': clone = new Map(); target.forEach((v, k) => clone.set(k, deepClone(v, map))); return clone; default: clone = new target.constructor(); map.set(target, clone); for (const key in target) { if (target.hasOwnProperty(key)) { clone[key] = deepClone(target[key], map); } } return clone; } }
|
3. 使用第三方库
4. 浏览器原生API(结构化克隆)
1 2 3 4 5 6 7 8 9 10
| function structuredClone(obj) { return new Promise(resolve => { const { port1, port2 } = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); }
const cloned = window.structuredClone(obj);
|
特点:
- 支持循环引用、更多内置类型(如
Blob
、File
)
- 兼容性问题:旧浏览器不支持
structuredClone
方法
性能对比与选型建议
- 简单数据:优先用
JSON
方案(注意数据类型限制)
- 复杂对象:推荐
Lodash.cloneDeep
或手动递归实现
- 浏览器环境:优先用
structuredClone
(兼容时)
- 高频深拷贝:可缓存函数或使用
Immutable.js
等不可变数据结构优化
循环引用测试:
1 2 3 4
| const obj = { a: 1 }; obj.self = obj; const cloned = deepClone(obj); console.log(cloned.self === cloned);
|