function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
default:
return null;
}
}
克隆Symbol类型:
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
克隆正则:
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
参考
深拷贝和浅拷贝的定义
浅拷贝
常用方法
深拷贝
常用方法
JSON.parse(JSON.stringify(obj)): 性能最快
递归进行逐一赋值
递归深拷贝
JSON.parse(JSON.stringify(obj))
虽然好用,但是存在很明显的缺陷,所以需要使用递归来完善深拷贝,由于事先无法知道拷贝深度,可以用递归来解决问题,在浅拷贝的基础上进行如下修改:很容易理解,如果有更深层次的对象可以继续递归直到属性为原始类型,这样我们就完成了一个最简单的深拷贝:
考虑数组
循环引用
这个存储空间,需要可以存储 key-value 形式的数据,且 key 可以是一个引用类型,我们可以选择 WeakMap 这种数据结构:
其他数据类型
在上面的代码中,我们其实只考虑了普通的 object 和 array 两种数据类型,实际上所有的引用类型远远不止这两个,还有很多,下面我们先尝试获取对象准确的类型。
获取数据类型
定义一个工具函数:
返回结果如下:
也可以定义一些常量:
上面的引用类型,可以简单的分为两类:
可以继续遍历的类型和不可以继续遍历的类型需要分开进行拷贝。
可继续遍历的类型
上面我们已经考虑的 object、array 都属于可以继续遍历的类型,因为它们内存都还可以存储其他数据类型的数据,另外还有 Map,Set 等都是可以继续遍历的类型,这里我们只考虑这四种,如果你有兴趣可以继续探索其他类型。
有序这几种类型还需要继续进行递归,我们首先需要获取它们的初始化数据,例如上面的[]和{},我们可以通过拿到 constructor 的 方式来通用的获取。
例如:const target = {}就是 const target = new Object()的语法糖。另外这种方法还有一个好处:因为我们还使用了原对象的构造方法,所以它可以保留对象原型上的数据,如果直接使用普通的{},那么原型必然是丢失了的。
下面,改写 clone 函数,对可继续遍历的数据类型进行处理:
不可继续遍历的类型
其他剩余的类型我们把它们统一归类成不可处理的数据类型,我们依次进行处理:
Bool、Number、String、String、Date、Error
这几种类型我们都可以直接用构造函数和原始数据创建一个新对象:克隆Symbol类型:
完整代码