深\浅拷贝

深/浅拷贝

深浅拷贝的核心区别在于是否递归复制对象的所有层级引用类型属性,常见实现方案如下:


浅拷贝实现

将原始对象或数组中的元素逐个复制到新对象或数组中

只会复制它们的引用,而不会创建这些嵌套对象或数组的新副本(改变原始对象会同时影响拷贝对象)

  1. Object.assign()

    1
    2
    3
    4
    5
    const obj = { a: 1, b: { c: 2 } };
    const copy = Object.assign({}, obj);
    console.log(copy); // { a: 1, b: { c: 2 } }
    obj.b.c = 3;
    console.log(copy.b.c); // 输出: 3 (修改了原始对象的嵌套对象,影响到了浅拷贝)
  2. 扩展运算符(...

    1
    2
    3
    4
    5
    const obj = { a: 1, b: { c: 2 } };
    const copy = { ...obj };
    console.log(copy); // { a: 1, b: { c: 2 } }
    obj.b.c = 3;
    console.log(copy.b.c); // 输出: 3 (同上)
  3. 数组方法
    sliceconcat等返回新数组,但嵌套对象仍共享引用:

    1
    2
    3
    4
    5
    const arr = [1, { x: 2 }];
    const copy = arr.slice();
    console.log(copy); // [1, { x: 2 }]
    arr[1].x = 3;
    console.log(copy[1].x); // 输出: 3 (同上)

深拷贝实现

递归地复制整个对象及其嵌套的对象或数组,创建一个完全独立的新对象或数组

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); // { a: 1, b: { c: 2 } }
obj.b.c = 3;
console.log(deepCopy.b.c); // 输出: 2 (未受影响)

缺点

  • 忽略undefinedSymbol、函数属性
  • 无法处理循环引用(如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)); // 对象中有循环引用(即对象内部包含对其自身的引用),JSON.stringify() 会抛出错误
console.log(copiedObj); // 输出: { name: 'example' } - undefined的属性,函数 都丢失了
console.log(typeof copiedObj.today); // 输出: string - 原本的 Date 对象变成了字符串
console.log(copiedObj.pattern); // 输出: undefined - 正则表达式丢失了

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); // 输出: undefined - 非可枚举属性丢失
console.log(copiedObj.visible); // 输出: "This is 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); // 输出: { name: 'Alice' } - 原型链上的 sayHello 方法丢失了
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;
// 其他类型如ArrayBuffer、TypedArray等类似处理
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. 使用第三方库
  • Lodash的_.cloneDeep

    1
    2
    import _ from 'lodash';
    const cloned = _.cloneDeep(obj);

    支持复杂类型(如BufferError对象)及循环引用。

4. 浏览器原生API(结构化克隆)
1
2
3
4
5
6
7
8
9
10
// 使用MessageChannel
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);

特点

  • 支持循环引用、更多内置类型(如BlobFile
  • 兼容性问题:旧浏览器不支持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); // true