对数据的深浅拷贝原生JS提供了很多的方法:
- 浅拷贝:
obj.slice(),Object.assign(obj),obj = [...obj1]等都能实现 - 深拷贝:最常用的是
JSON.parse(JSON.stringify(obj))这种深拷贝是不彻底的, 数据中含有下列类型的内容时:
- Symbol/undefined/function 直接不显示;
- bigint 直接报错;
- 正则 转变成一个空对象;
- Date对象 转换成字符串后就转换不回来了.
下面是深拷贝的实现思路及代码:
/* 深拷贝其实就是递归调用浅拷贝, 所以先来实现一个浅拷贝:
* 思路: 通过传入值(obj)的构造器, new一个跟obj相同类型的变量(newObj),
* 遍历传入值的key值(keyList), 然后把obj的每项值映射到newObj上。
*/
~function(){
function getType(obj){
return Object.prototype.toString.call(obj)
}
function shallowClone(obj){
// 不知道传进来的是什么类型的数据, 通过new一个constructor得到相同类型的变量;
// new实例没参数可以不用写()
let newObj = new obj.constructor,
keyList = Object.keys(obj),
type = getType(obj);
// 处理date/正则类型, 直接用构造器new一下就是个新值
if(/^(date|regexp)$/i.test(type)){return new newObj(obj)}
// 处理bigint/Symbol类型, 这两种类型是不能new的
if(/^(bigint|symbol)$/i.test(type)){return Object(obj)}
// 处理错误对象: 用构造器new错误对象的message值
if(/^error$/i.test(type)){return new newObj(obj.message)}
// 处理函数
if(/^function$/i.test(type)){
// 返回一个函数, 构建一个新的作用域
return function(){
// this指向调用这个函数的上下文
return obj.call(this, ...arguments)
}
}
if(/^(object|array)$/i.test(type)){
keyList.map(key => newObj[key] = obj[key])
return newObj
}
// 基本类型直接返回即可
return obj
}
function deepClone(obj, cache = new Set()){
let newObj = new obj.constructor,
keyList = Object.keys(obj),
type = getType(obj);
// 非数组或对象类型直接调用浅拷贝方法
if(!/^(object|array)$/i.test(type)){return shallowClone(obj)}
/*
* 为避免出现对象的无线套娃情况, 造成无限递归
* 使用new Set()的add(), 和has方法 做个缓存
* 因对象的key值不能为对象, 所以这里不好用对象的key是唯一值的机制做
*/
if(cache.has(obj))return obj
cache.add(obj)
// 判断下级还是数组和对象, 就继续浅拷贝, 即递归调用
keyList.map(key => newObj[key] = deepClone(obj[key], cache))
return newObj
}
}()