本文已参与「新人创作礼」活动,一起开启掘金创作之路。
45行代码,教你实现一个属于自己的深拷贝工具函数(deepClone)
前言
近日翻开自己封装的工具类函数时,突然发现了自己以前封装的深拷贝工具函数,写过js的小伙伴应该都理解,关于引用类型的那些事,借此来记录和分分析一下当时封装函数的一些思路。
深拷贝与浅拷贝的区别
只针对引用类型考虑,像一些基本数据类型(number,String等)就没有浅拷贝这一说法。
1、js原生的赋值只是将对象指向的地址赋值给了另一个对象,故此只要其中一个对象修改了另一个对象也会随之变化。
2、有一种说法浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响,也有一种说法是引用赋值类型就是浅拷贝,但在我的理解中,只要拷贝对象与源对象能够互相影响,那就是浅拷贝。
3、深拷贝要做到的就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。
关于JSON.stringify的那些事
简单拷贝使用JSON.stringify-JSON.parse倒是没问题,在清楚接口回传回来的数据结构时使用是没问题的,同时也还算方便,但是一旦碰到以下几种情况,请千万千万不要使用JSON.stringify。
1、当对象中有时间类型的元素时候。
2、当对象中有undefined类型或function类型的数据时。
3、当对象中有NaN、Infinity和-Infinity这三种值的时候。
4、当对象循环引用的时候。
5、具有不可枚举的属性值时。
具体为什么咱就不具体展开说了,由于篇幅限制,有兴趣小伙伴们可以翻阅相关的JSON.stringify文章讲解。
思路分析
- 对象分类
- 递归
- 缓存解决循环引用的问题
开工,上代码
function deepClone(obj) {
let cache = [] // 创建一个缓存对象
function clone(obj) {
if (obj instanceof Object) {
let cachedDist = findCache(obj)
if (cachedDist) {
return cachedDist // 如果找到缓存,证明存在循环引用或属性引用了相同对象,直接返回引用就可以了
} else {
let dist
if (obj instanceof Object) {
if (obj instanceof Array) { // 不同类型的对象对应不同的处理方法
dist = []
} else if (obj instanceof Function) {
dist = function () {
return obj.call(this, ...arguments)
}
} else if (obj instanceof RegExp) {
dist = new RegExp(obj.obj, obj.flags)
} else if (obj instanceof Date) {
dist = new Date(obj)
} else {
dist = {}
}
cache.push([obj, dist]) // 把源对象和新对象放进缓存列表
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 只复制不在原型上的属性
dist[key] = clone(obj[key]) // 递归
}
}
return dist
}
}
}
return obj
}
function findCache(obj) {
for (let i = 0; i < cache.length; i++) {
if (cache[i][0] === obj) {
return cache[i][1]
}
}
}
return clone(obj)
}
结尾
当然这样处理深拷贝的方式不是最完美的,如果想再进一步解决一些问题的话,推荐可以去看Lodash.cloneDeep() ,当然,直接使用loadsh也是没有问题的。
最后感谢各位看官能够看到这里~