乐趣区

用js写一个简短实用的深拷贝

注意事项

  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
退出移动版