js实现深拷贝

9/21/2022 JS

摘要

记录JS中实现深拷贝的原理

// 复杂数据结构,处理深拷贝,object1 => clonedObject 属性修改, 互不影响
let obj1 = {
  array: [1, 2],
  arrayOfObj: [{ a: 3, b: 4 }],
  obj: { c: 5 },
  fn: function () { console.log(6) },
  reg: /7/,

  // ES6 Map, Set
  map: new Map([['d', 8], ['e', 9]]),
  set: new Set([10, 11]),

  num: 12,
  nulVal: null, // JS中null是属于对象, 有点bug
}

// 出现循环引用, 这种情况是可以发生的,就如数据结构中的循环链表,每个节点node就是一个object
// 这里obj1 和 obj2 互相引用
let obj2 = {}
obj2.to  = obj1
obj1.to =  obj2

// JSON序列化 => 反序列化 => 序列化
let cloned = JSON.parse(JSON.stringify(obj1))
console.log("JSON序列化以后", cloned)
// 我们会发现 function, RegExp, Set, Map,没有拷贝过来,【因为这些是JSON中本不存在的数据类型】

// 使用工具库 lodash underscore, 这类是第三方的工具库, 实际工作中可能需要我们呢自己封装

// 定义缓存map
// 使用WeakMap是为了使用弱引用,避免对象已经清理, 而map中对于对象的引用未清除的情况
let cached = new WeakMap()


function deepClone(obj) {
  // 宏观思维, 处理不同类型, 两大分类:基本 + 引用
  // 注意null也是对象
  // 基本数据类型 + null + function name(params)
  // 注意!  !null 是输出true, 所以这里需要加上!obj这个判断
  // 同理 !obj 还可以判断 undefined, 空串
  if (typeof obj != 'object' || !obj) {
    return obj
  }

  // 判断是否缓存中有值,如果有,返回处理后的值,不向下执行,避免无限递归
  if (cached.has(obj)) {
    return cached.get(obj)
  }


  // 大致规则是es6之后的类型判断很多都能使用instanceof
  // 判断正则表达式类型
  if (obj instanceof RegExp) {
    let tmp = new RegExp(obj)
    cached.set(obj, tmp)
    return tmp
  }
  // 判断Map类型
  else if (obj instanceof Map) {
    let tmp = new Map()
    // 做缓存
    cached.set(obj, tmp)
    // 遍历Map类型的obj
    // 或者使用forEach()方法来遍历,是map和set独有的
    // obj.forEach((val,key)=>{tmp.set(deepClone(key), deepClone(val))})
    // 因为map的key和val是有可能是引用数据类型的,所以这里需要递归的调用
    for (let [k, v] of obj) {
      tmp.set(deepClone(k), deepClone(v))
    }
    return tmp
  }
  // 判断Set类型
  else if (obj instanceof Set) {
    let tmp = new Set()
    // 做缓存
    cached.set(obj, tmp)
    // 遍历Set类型的obj
    for (let v of obj) {
      tmp.add(deepClone(v))
    }
    return tmp
  }
  else {
    // 数组和对象 {} []  new Object || Array() obj.constructor
    let tmp = new obj.constructor()
    cached.set(obj, tmp)
    // 遍历数组或对象
    for (let key in obj) {
      tmp[key] = deepClone(obj[key])
    }
    return tmp
  }
}

let clonedObj = deepClone(obj1)
console.log("deepClone之后", clonedObj)
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Last Updated: 11/30/2022, 9:05:57 AM
夜空中最亮的星
逃跑计划