文章

深拷贝的实现思路深度解析

深拷贝的实现思路深度解析

深入理解 JavaScript 深拷贝的实现原理,掌握递归拷贝、类型判断、循环引用处理等核心技术。

一、背景与问题

浅拷贝只复制第一层,嵌套对象仍是引用:

1
2
3
4
5
const obj = { user: { name: 'Alice' } };
const shallowCopy = { ...obj };

shallowCopy.user.name = 'Bob';
console.log(obj.user.name);  // 'Bob' - 原对象被修改!

深拷贝:递归复制所有层级,创建完全独立的副本。

1
2
3
4
5
const obj = { user: { name: 'Alice' } };
const deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.user.name = 'Bob';
console.log(obj.user.name);  // 'Alice' - 原对象不受影响

二、核心概念与定义

2.1 深拷贝的定义

递归复制对象的所有层级属性,使新旧对象完全独立:

1
2
3
4
5
6
7
8
9
10
原对象                           深拷贝后
┌─────────────┐              ┌─────────────┐
│ name: 'Alice'│ ──────────→ │ name: 'Alice'│
│ info: ──────┼──┐           │ info: ──────┼──┐
└─────────────┘  │           └─────────────┘  │
                 │                             │
                 ▼                             ▼
            ┌──────────┐                 ┌──────────┐
            │city:'BJ' │ ────────────→   │city:'BJ' │
            └──────────┘    (新对象)     └──────────┘

2.2 深拷贝需要处理的数据类型

类型处理方式
基本类型直接返回
Object递归复制属性
Array递归复制元素
Datenew Date(value)
RegExpnew RegExp(source, flags)
Mapnew Map(entries)
Setnew Set(values)
Function直接引用(或重新创建)
Symbol直接引用(或 Symbol(description))
null/undefined直接返回

三、最小示例

3.1 JSON 方法(最简单)

1
2
3
4
5
6
7
8
function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

const obj = { a: 1, b: { c: 2 } };
const copy = deepClone(obj);
copy.b.c = 3;
console.log(obj.b.c);  // 2

局限性

  • 无法处理 Function、Symbol、undefined
  • 无法处理循环引用
  • Date 会变成字符串
  • RegExp 会变成空对象

3.2 递归实现(基础版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function deepClone(obj) {
  // 基本类型直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 数组
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item));
  }
  
  // 对象
  const copy = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key]);
    }
  }
  return copy;
}

四、核心知识点拆解

4.1 类型判断

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
38
39
// 获取精确类型
function getType(value) {
  const type = typeof value;
  
  // 基本类型
  if (type !== 'object') {
    return type;
  }
  
  // null
  if (value === null) {
    return 'null';
  }
  
  // 使用 Object.prototype.toString
  const toString = Object.prototype.toString.call(value);
  const map = {
    '[object Object]': 'object',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regexp',
    '[object Map]': 'map',
    '[object Set]': 'set',
    '[object WeakMap]': 'weakmap',
    '[object WeakSet]': 'weakset',
    '[object Function]': 'function',
    '[object Symbol]': 'symbol',
    '[object BigInt]': 'bigint',
    '[object Arguments]': 'arguments'
  };
  
  return map[toString] || 'unknown';
}

// 使用
console.log(getType({}));        // 'object'
console.log(getType([]));        // 'array'
console.log(getType(new Date())); // 'date'
console.log(getType(null));      // 'null'

4.2 特殊类型处理

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function deepClone(obj, hash = new WeakMap()) {
  // null 或非对象
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 循环引用检查
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  const type = getType(obj);
  let copy;
  
  switch (type) {
    case 'array':
      copy = [];
      hash.set(obj, copy);
      for (let i = 0; i < obj.length; i++) {
        copy[i] = deepClone(obj[i], hash);
      }
      break;
      
    case 'date':
      copy = new Date(obj);
      break;
      
    case 'regexp':
      copy = new RegExp(obj.source, obj.flags);
      break;
      
    case 'map':
      copy = new Map();
      hash.set(obj, copy);
      obj.forEach((value, key) => {
        copy.set(deepClone(key, hash), deepClone(value, hash));
      });
      break;
      
    case 'set':
      copy = new Set();
      hash.set(obj, copy);
      obj.forEach(value => {
        copy.add(deepClone(value, hash));
      });
      break;
      
    case 'object':
    default:
      copy = {};
      hash.set(obj, copy);
      // 复制 Symbol 属性
      const allKeys = [
        ...Object.keys(obj),
        ...Object.getOwnPropertySymbols(obj)
      ];
      for (const key of allKeys) {
        copy[key] = deepClone(obj[key], hash);
      }
      break;
  }
  
  return copy;
}

4.3 循环引用处理

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
// 问题:循环引用导致无限递归
const obj = { a: 1 };
obj.self = obj;  // 循环引用

// JSON 方法会报错
JSON.parse(JSON.stringify(obj));  // TypeError: Converting circular structure to JSON

// 解决:使用 WeakMap 记录已拷贝对象
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 检查是否已拷贝
  if (hash.has(obj)) {
    return hash.get(obj);  // 返回已拷贝的副本
  }
  
  const copy = Array.isArray(obj) ? [] : {};
  hash.set(obj, copy);  // 记录映射关系
  
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key], hash);
    }
  }
  
  return copy;
}

// 测试
const copy = deepClone(obj);
console.log(copy.self === copy);  // true

4.4 为什么用 WeakMap?

1
2
3
4
5
6
7
8
9
10
11
12
13
// WeakMap vs Map

// Map:强引用,阻止垃圾回收
const map = new Map();
let obj = { a: 1 };
map.set(obj, 'value');
obj = null;  // obj 不会被回收,因为 map 还引用它

// WeakMap:弱引用,不阻止垃圾回收
const weakMap = new WeakMap();
let obj = { a: 1 };
weakMap.set(obj, 'value');
obj = null;  // obj 可以被回收

优势

  • 不阻止垃圾回收
  • 键必须是对象(恰好用于记录对象映射)
  • 不需要手动清理

五、实战案例

5.1 完整深拷贝实现

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function deepClone(source, hash = new WeakMap()) {
  // 基本类型、null、undefined
  if (source === null || source === undefined) {
    return source;
  }
  
  // 基本包装类型
  if (typeof source !== 'object') {
    return source;
  }
  
  // 循环引用
  if (hash.has(source)) {
    return hash.get(source);
  }
  
  // 获取类型
  const type = Object.prototype.toString.call(source);
  let clone;
  
  switch (type) {
    case '[object Date]':
      return new Date(source);
      
    case '[object RegExp]':
      return new RegExp(source.source, source.flags);
      
    case '[object Map]':
      clone = new Map();
      hash.set(source, clone);
      source.forEach((value, key) => {
        clone.set(deepClone(key, hash), deepClone(value, hash));
      });
      return clone;
      
    case '[object Set]':
      clone = new Set();
      hash.set(source, clone);
      source.forEach(value => {
        clone.add(deepClone(value, hash));
      });
      return clone;
      
    case '[object Array]':
      clone = [];
      hash.set(source, clone);
      for (let i = 0; i < source.length; i++) {
        clone[i] = deepClone(source[i], hash);
      }
      return clone;
      
    case '[object Object]':
      clone = Object.create(Object.getPrototypeOf(source));
      hash.set(source, clone);
      // 复制所有自有属性(包括 Symbol)
      const keys = Reflect.ownKeys(source);
      for (const key of keys) {
        clone[key] = deepClone(source[key], hash);
      }
      return clone;
      
    case '[object Function]':
      // 函数通常不拷贝,直接返回引用
      return source;
      
    default:
      return source;
  }
}

5.2 带选项的深拷贝

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function deepClone(source, options = {}) {
  const {
    depth = Infinity,      // 最大深度
    ignoreKeys = [],       // 忽略的键
    customizer = null      // 自定义拷贝函数
  } = options;
  
  function clone(value, currentDepth, hash) {
    // 深度限制
    if (currentDepth > depth) {
      return value;
    }
    
    // 基本类型
    if (value === null || typeof value !== 'object') {
      return value;
    }
    
    // 循环引用
    if (hash.has(value)) {
      return hash.get(value);
    }
    
    // 自定义拷贝
    if (customizer) {
      const result = customizer(value);
      if (result !== undefined) {
        return result;
      }
    }
    
    const copy = Array.isArray(value) ? [] : {};
    hash.set(value, copy);
    
    for (const key in value) {
      if (value.hasOwnProperty(key) && !ignoreKeys.includes(key)) {
        copy[key] = clone(value[key], currentDepth + 1, hash);
      }
    }
    
    return copy;
  }
  
  return clone(source, 0, new WeakMap());
}

// 使用
const copy = deepClone(obj, {
  depth: 3,
  ignoreKeys: ['password'],
  customizer: (value) => {
    if (value instanceof Date) {
      return new Date(value);
    }
  }
});

六、底层原理

6.1 递归与栈溢出

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
38
39
// 深层嵌套可能导致栈溢出
const obj = {};
let current = obj;
for (let i = 0; i < 100000; i++) {
  current.nested = {};
  current = current.nested;
}

deepClone(obj);  // RangeError: Maximum call stack size exceeded

// 解决:使用迭代代替递归
function deepCloneIterative(source) {
  const root = {};
  const stack = [{ source, target: root }];
  const hash = new WeakMap();
  hash.set(source, root);
  
  while (stack.length > 0) {
    const { source, target } = stack.pop();
    
    for (const key in source) {
      if (source.hasOwnProperty(key)) {
        const value = source[key];
        
        if (value === null || typeof value !== 'object') {
          target[key] = value;
        } else if (hash.has(value)) {
          target[key] = hash.get(value);
        } else {
          target[key] = Array.isArray(value) ? [] : {};
          hash.set(value, target[key]);
          stack.push({ source: value, target: target[key] });
        }
      }
    }
  }
  
  return root;
}

6.2 原型链处理

1
2
3
4
5
6
7
8
9
10
11
12
// 是否保持原型链?
const obj = Object.create({ proto: 'value' });
obj.own = 'own';

// 方式一:不保持原型链
const copy1 = { ...obj };
console.log(copy1.proto);  // undefined

// 方式二:保持原型链
const copy2 = Object.create(Object.getPrototypeOf(obj));
Object.assign(copy2, obj);
console.log(copy2.proto);  // 'value'

七、高频面试题解析

Q1:JSON.parse(JSON.stringify()) 的局限性?

答案

  1. 无法处理 Function、Symbol、undefined(会丢失)
  2. 无法处理循环引用(会报错)
  3. Date 变成字符串
  4. RegExp 变成空对象
  5. NaN、Infinity 变成 null
  6. 会忽略不可枚举属性
1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
  fn: () => {},           // 丢失
  sym: Symbol('s'),       // 丢失
  undef: undefined,       // 丢失
  date: new Date(),       // 变成字符串
  regex: /test/g,         // 变成空对象 {}
  nan: NaN,               // 变成 null
  infinity: Infinity      // 变成 null
};

const copy = JSON.parse(JSON.stringify(obj));
console.log(copy);
// { date: "2026-05-05T00:00:00.000Z", regex: {}, nan: null, infinity: null }

Q2:如何处理循环引用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用 WeakMap 记录已拷贝对象
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  const copy = Array.isArray(obj) ? [] : {};
  hash.set(obj, copy);
  
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key], hash);
    }
  }
  
  return copy;
}

Q3:structuredClone 是什么?

答案:浏览器原生深拷贝 API,支持更多类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 浏览器原生方法
const obj = {
  date: new Date(),
  map: new Map([['a', 1]]),
  set: new Set([1, 2, 3]),
  regex: /test/g,
  nested: { a: 1 }
};

const copy = structuredClone(obj);

// 支持的类型
// ✅ Object, Array, Date, RegExp, Map, Set, ArrayBuffer, TypedArray
// ❌ Function, Symbol, DOM 节点

// 也支持循环引用
const circular = { self: null };
circular.self = circular;
const copy2 = structuredClone(circular);
console.log(copy2.self === copy2);  // true

Q4:深拷贝的性能优化?

1
2
3
4
5
6
7
8
9
10
11
// 1. 浅层对象用浅拷贝
function smartClone(obj, depth = 0) {
  if (depth === 0 || isShallow(obj)) {
    return { ...obj };
  }
  return deepClone(obj);
}

// 2. 使用迭代代替递归(避免栈溢出)
// 3. 缓存已拷贝对象(循环引用)
// 4. 跳过不需要拷贝的属性

八、总结与扩展

核心要点

  1. 类型判断:使用 Object.prototype.toString 精确判断类型
  2. 递归拷贝:遍历所有属性,递归处理嵌套对象
  3. 循环引用:使用 WeakMap 记录已拷贝对象
  4. 特殊类型:Date、RegExp、Map、Set 需要特殊处理

方法选择指南

场景推荐方法
简单对象(无特殊类型)JSON.parse(JSON.stringify())
复杂对象(现代浏览器)structuredClone()
需要自定义处理手写递归函数
生产环境Lodash _.cloneDeep()

扩展阅读

  • structuredClone:HTML 标准原生 API
  • Lodash.cloneDeep:生产级深拷贝实现
  • Immer:不可变数据操作,内部使用深拷贝
  • MessageChannel:通过 postMessage 实现深拷贝

深拷贝是 JavaScript 中的核心技能,理解其实现原理有助于正确处理复杂数据结构。

本文由作者按照 CC BY 4.0 进行授权