同系列文章请查看:Github:pre-interview
ES6深拷贝函数封装
前置:WeakMap数据类型
Map与WeakMap区别:
- Map的键名可以是任意类型({}、[])
- WeakMap的键名只能是对象
例:从垃圾回收来感受WeakMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const oBtn1 = document.querySelector('#btn1') const oBtn2 = document.querySelector('#btn1')
oBtn1.addEventListener('click', handleClick1, false) oBtn2.addEventListener('click', handleClick2, false)
function handleClick1() {} function handleClick2() {}
oBtn1.remove() oBtn2.remove()
handleClick1 = null; handleClick2 = null
|
使用WeakMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const oBtn1 = document.querySelector('#btn1') const oBtn2 = document.querySelector('#btn1')
const oBtnMap = new WeakMap()
oBtnMap.set(oBtn1, handleClick1) oBtnMap.set(oBtn2, handleClick2)
oBtn1.addEventListener('click', handleClick1, false) oBtn2.addEventListener('click', handleClick2, false)
function handleClick1() {} function handleClick2() {}
|
换一个角度看,WeakMap的键名必须是对象,弱引用才有意义。
深拷贝函数封装
拷贝之前,需要先判断原始值(origin
)的数据类型再进行下一步操作。
function当做静态数据类型,不做特殊处理。
1)关于null和undefined的判断:
1 2 3 4
| var a = undefined; console.log(a == null); console.log(a === null);
|
使用 origin === undefined
表达式就能判断origin
是否为null和undefined值。
2)判断Date、RegExp类型数据:
1 2 3 4 5 6
| if (origin instanceof Date) { return new Date(origin); } if (origin instanceof RegExp) { return new RegExp(origin); }
|
3)判断 {} 或 []:
使用构造器
1 2
| const obj = {} console.log(obj)
|
打印结果:

可以看到,字面量obj是通过其原型上的constructor使用new Object构造出来的,那么是否可以自己手动构造一个obj呢?
1 2 3 4 5
| const obj = {} const newObj = new obj.constructor() console.log(obj) newObj.a = 1 console.log(newObj);
|
同理,数组:
1 2 3 4 5
| const arr = [] const newArr = new arr.constructor() console.log(arr); newArr.push(1) console.log(newArr);
|
知道这点,直接使用 const target = new origin.constructor()
生成一个新 {} 或 [] 即可,不用再判断了。
封装函数1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function deepClone(origin) { if (origin === undefined || typeof origin !== 'object') { return origin; } if (origin instanceof Date) { return new Date(origin); } if (origin instanceof RegExp) { return new RegExp(origin); }
const target = new origin.constructor()
for (let k in origin) { if (origin.hasOwnProperty(k)) { target[k] = deepClone(origin[k]) } }
return target; }
|
测试1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var obj = { name: 'yesmore', age: 18, info: { hobby: ['travel', 'piano', { a: 1 }], career: { teacher: 4, engineer: 9 } } };
const newObj = deepClone(obj) newObj.info.hobby[2].a = 1234 console.log(newObj);
|
结果:

测试2
1 2 3 4 5 6 7 8
| let test1 = {} let test2 = {} test2.test1 = test1 test1.test2 = test2 console.log(deepClone(test2));
报错: Uncaught RangeError: Maximum call stack size exceeded
|
原因:test1和test2分别作为属性值赋值给对方,在拷贝时,会发生重复拷贝,简而言之,就是缺少记录是否拷贝。
解决:使用 hashMap 哈希表方式记录键名是否存在,通过 WeakMap 实现。
封装函数2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function deepClone(origin, hashMap = new WeakMap()) { if (origin === undefined || typeof origin !== 'object') { return origin; } if (origin instanceof Date) { return new Date(origin); } if (origin instanceof RegExp) { return new RegExp(origin); }
const hashKey = hashMap.get(origin); if (hashKey) { return hashKey }
const target = new origin.constructor() hashMap.set(origin, target) for (let k in origin) { if (origin.hasOwnProperty(k)) { target[k] = deepClone(origin[k], hashMap) } }
return target; }
|
测试3
1 2 3 4 5
| let test1 = {} let test2 = {} test2.test1 = test1 test1.test2 = test2 console.log(deepClone(test2));
|
结果:

Tips:
Please indicate the source and original author when reprinting or quoting this article.