# 手动实现一个深拷贝

简单递归,深层遍历对象

function deepClone (obj) {
    let newObj = {}
    for(let item in obj) {
        if(typeof obj[item] !== 'object') {
            newObj[item] = obj[item]
        } else {
            newObj[item] = deepClone(obj[item])
        }
    }
    return newObj
}
var obj1 = {
    name: 'linjiaheng',
    age: 23,
    partner: ['laihengcong', 'quanzhiyuan', 'penghaohao']
}
var obj2 = deepClone(obj1)
obj2.name = 'xielin'
obj2.age = 24
obj2.partner[0] = 'xiaoxiao'
console.log(obj2)
console.log(obj1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

img

# 存在的问题

  1. 传入的对象参数没有校验,可能是null/Date/RegExp/Array
// 判断传入的参数
function deepClone(obj) {
    if(!isObject(obj)) return obj;
    if(obj == null) return null;
    if(obj instanceof Date) return new Date(obj)
    if(obj instanceof RegExp) return new RegExp(obj);
    if(Object.prototype.toString.call(obj) === '[object Array]') {
        // 处理数组
    }
}
1
2
3
4
5
6
7
8
9
10
  1. 判断对象
function isObject(x) {
    return Object.prototype.toString.call(x) === '[object Object]'
}
1
2
3
  1. 若为数组,或者为Set Map weakSet weakMap
// 考虑数组兼容
var target = Array.isArray(obj) ? [] : {}
1
2
  1. 解决循环引用问题

所谓的循环检测非常简单,我们设置一个数组或者哈希表用来存储已经拷贝过的对象,检测当前对象是否已经存在于哈希表中,如果有,取出该值,然后返回。

function deepClone(obj, map=new Map()) {
    if(!isObject(obj)) return obj;
    
    // 循环检测,如果存在直接返回该值
    if(map.has(obj)) return map.get(obj)

    // 判断数组
    var target = Array.isArray(obj) ? [] : {}

    map.set(obj, target)

    // 遍历拷贝
    for(let item in obj) {
        if(obj.hasOwnProperty(key)) {
            if(isObject(obj[key])) {
                target[key] = deepClone(obj[key], map)
            } else {
                target[key] = obj[key]
            }
        }
    }
    return target
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  1. 递归容易爆栈(层次很深的时候) 之前的文章破解递归爆栈

# 按照数据类型实现一个深拷贝

// 判断类型
function getType (target) {
    return Object.prototype.toString.call(target).slice(8, -1)
}
// 判断是否是原始类型类型.
// 对应可引用的数据类型,需要递归遍历;对应不可引用的数据类型,直接复制即可
function isReferenceType (target) {
    let type = typeof target
    return (target !== null && (type === 'object' || type === 'function'))
}
// 获取原型上的方法
function getInit (target) {
    let ClassNames = target.constructor
    return new ClassNames()
}
// 引用类型
const mapTag = 'Map'
const setTag = 'Set'
const arrayTag = 'Array'
const objectTag = 'Object'

// 不可引用类型
const boolTag = 'Boolean'
const dateTag = 'Date'
const errorTag = 'Error'
const numberTag = 'Number'
const regexpTag = 'RegExp'
const stringTag = 'String'
const symbolTag = 'Symbol'
const bufferTag = 'Uint8Array'

let deepTag = [mapTag, setTag, arrayTag, objectTag]
function deepClone(target,map=new Map()) {
    let type = getType(target)
    let isOriginType = isReferenceType(target) 
    if(!isOriginType) {return target} // 对于不可引用的数据类型,直接复制即可
    let cloneTarget
    if(deepTag.includes(type)){
        cloneTarget = getInit(target)
    }

    // 防止循环引用
    if(map.get(target)) {
        return map.get(target)
    }
    map.set(target,cloneTarget)

    // 如果是mapTag类型
    if(type === mapTag) {
        target.forEach((v,key) => {
            cloneTarget.set(key,deepClone(v,map))
        })
        return cloneTarget
    }
    if(type === setTag) {
        target.forEach((v) => {
            cloneTarget.add(deepClone(v,map))
        })
        return cloneTarget
    }
    if (type === arrayTag) {
        target.forEach((v, i) => {
          cloneTarget[i] = deepClone(v, map)
        })
        return cloneTarget
    }
     // 如果是 objectTag 类型
    if (type === objectTag) {
        let array = Object.keys(target)
        array.forEach((i, v) => {
            cloneTarget[i] = deepClone(target[i], map)
        })
        return cloneTarget
    }
}

const map = new Map()
map.set('key', 'value')
map.set('name', 'kaka')

const set = new Set()
set.add('11').add('12')
console.log(JSON.stringify(map))
console.log(JSON.stringify(new Set([1,2])))

const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child'
  },
  field4: [
    2, 8
  ],
  empty: null,
  map,
  set,
  test: {
      name: 'linjiaheng',
      skill: ['js','css']
  }
}
target.target = target
const target1 = deepClone(target)
target.test.name = '谢琳'
console.log('🍎', target)
console.log('🍌', target1)
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
97
98
99
100
101
102
103
104
105
106
107