简易版
function deepCopy(target){/*类型检查*/if(typeof target === 'object' && target != null){const cloneTarge = Array.isArray(target) ? [] : {};for(let prop in target){if(target.hasOwnProperty(prop)){cloneTarge[prop] = target[prop]}}return cloneTarge}else{return target}}
三个问题
1。解决循环引用
let obj = {val : 100}obj.target = obj

这就是循环引用
创建一个Map。记录下已经拷贝过的对象,如果说已经拷贝过,那直接返回它行了。
map和weakmap
mapES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
const m = new Map()const o = {p:'hello world'}m.set(o,'content')m.get(o) //contentm.has(o) //truem.delete(o)m.has(o) //false/*size属性返回 Map 结构的成员总数*/m.set('1','2')m.set('3','4')console.log(m.size)
Map.prototype.set(key, value)set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键set方法返回的是当前的Map对象,因此可以采用链式写法。m.set().set().set()Map.prototype.get(key)get方法读取key对应的键值,如果找不到key,返回undefined。Map.prototype.has(key)has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。Map.prototype.delete(key)delete方法删除某个键,返回true。如果删除失败,返回false。Map.prototype.clear()clear方法清除所有成员,没有返回值。Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历 Map 的所有成员
/*keys*/for(let key of m.keys()){console.log(key)/*13*/}/*values*/for(let value of m.values()){console.log(value)/*24*/}/*entries*/for(let item of m.entries()){console.log(item[0],item[1])/*1 23 4*/}/*foreach*/for(let [key,value] of m){console.log(key,value)/*1 23 4*/}
weakmapWeakMap结构与Map结构类似,也是用于生成键值对的集合。WeakMap与Map的区别有两点。首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。WeakMap的键名所指向的对象,不计入垃圾回收机制。
// WeakMap 也可以接受一个数组,// 作为构造函数的参数const k1 = [1, 2, 3];const k2 = [4, 5, 6];const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
/*symbolSymbol,表示独一无二的值Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象*/var obj2 = {}var s1 = Symbol('a')obj2[s1] = 'hello'/*Object.getOwnPropertySymbols()可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。*/Object.getOwnPropertySymbols(obj2)//[Symbol(a)]/*hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。obj.hasOwnProperty(prop)prop要检测的属性的 String 字符串形式表示的名称,或者 Symbol。返回值用来判断某个对象是否含有指定的属性的布尔值 Boolean。使用 hasOwnProperty 作为属性名JavaScript 并没有保护 hasOwnProperty 这个属性名,因此,当某个对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty 获得正确的结果:// 也可以使用 Object 原型上的 hasOwnProperty 属性*/var foo = {hasOwnProperty: function() {return false;},bar: 'Here be dragons'};Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
利用上述知识,可以做出一个深拷贝的函数
const deepcopy2 = (target, hash = new WeakMap()) => {//对于参数处理if(typeof target !== 'object' || target === null){return target}if(hash.get(target)){return hash.get(target)}const clonetarget = Array.isArray(target) ? [] : {}hash.set(target,clonetarget)//针对symbol属性const symKeys = Object.getOwnPropertySymbols(target);if(symKeys.length){symKeys.forEach(symKey => {if(typeof target[symKey] === 'object' && target[symKey] !== null){clonetarget = deepcopy2(target[symKey])}else{clonetarget[symKey] = target[symKey]}});}for(const i in target){if(Object.prototype.hasOwnProperty.call(target,i)){clonetarget[i] = typeof target[i] === 'object' && target[i] !== null ? deepcopy2(target[i],hash) : target[i]}}return clonetarget}
/*分析首先先判断target是不是对象,如果不是直接返回target 因为基本类型数据拷贝时互相不影响之后设置一个weakmap,用来缓存数据,因为weakmap的键名所指向的对象不计入垃圾回收机制判断一下hash有没有target所指向的值,因为target是键名,有就返回,可以避免重复遍历创建一个变量,用来传进来的symbol属性做判断,获取target的所有属性名如果属性名不为空,那么就遍历如果对象的键值不为object或者array这样的对象,那么就直接传值如果是object或者array这样的对象,那么就递归一次,设置clonetarget[键] = target[键],这样将target的键值赋值给clonetarget,之后返回clonetarget递归一次之后,在将clonetarget[键] = target[键],这样如果target键值有Object或者array这样的对象的话,那么就用递归,将键值赋给clonetarget剩下的思路也是一样的*/
