注意事项

  1. 区分引用类型和普通类型
  2. 注意循环引用的问题
  3. 区分内部变量和原型链中的变量

代码

function getType(arg) {
    return Object.prototype.toString.call(arg).replace(/\[object (.+)\]/,'$1')
}
function deepCopy(arg, map) {
    let typeID = ['Array','Object'].indexOf(getType(arg))
    if (typeID < 0) return arg
    let rtn = typeID ? {} : [] 
    map = map || new WeakMap()
    map.set(arg, rtn)
    Object.keys(arg).map(item => {
        if (map.has(arg[item])) {
            rtn[item] = map.get(arg[item])
        } else {
            rtn[item] = deepCopy(arg[item],map)
        }
    })
    return rtn
}

解析

  1. 深拷贝的实现思路无外乎对基本类型直接返回,引用类型进行递归。
  2. 为了避免循环引用导致的栈溢出,使用WeakMap存储 被拷贝项=>拷贝项 结构,由于WeakMap对于键名是弱引用,因此键名必须是引用类型,因此此处同时还能起到验证放入map中的项不是基础类型的作用。
  3. 对于遍历的每个对象,都先在map中寻找是否有对应的项,有则说明此项指向了之前遍历过的项,即存在循环引用,此时直接取出此 被拷贝项 对应的 拷贝项 的值进行赋值。
  4. 此处循环之所以采用Object.keys(arg).map而不用for(let i in arg),是为了避免遍历出原型链中的变量。例如:
a={b:2};a.__proto__.c=4
console.log(Object.keys(a)) // ['b']
for(let i in a){console.log(i)} // b c

验证

a={b:{c:[]}};a.b.c[0]=a;;a.b.c[1]=NaN;a.b.c[2]=null;
b=deepCopy(a)
b.b.c[0]===b //true

goblin_pitcher
590 声望30 粉丝

道阻且长