手写深拷贝
深拷贝是什么
在 JS 中,所有的拷贝 API 都是浅拷贝,比如数组的拷贝,我们一般使用Array.prototype.slice来拷贝一个数组,但是对于嵌套数组,就会只拷贝其中的引用。
js1const arr = [ 2 [1, 2, 3], 3 [4, 5, 6], 4] 5const arr2 = arr.slice() 6arr2[0].push(666) 7console.log(arr) //[[1, 2, 3, 666], [4, 5, 6]]
上面的 arr2 是拷贝后的数组,arr2 改变了同样会影响到 arr 的值,所以这就不是深拷贝。
官方解释是这样的
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
我所理解的深拷贝,就是当我克隆一个东西出来之后,跟原来的完全不相交。

序列化与反序列化的深拷贝
在工作中,我们一般都会使用序列化进行深拷贝,就是采用JSON.stringify和JSON.parse来进行深拷贝
js1const obj = { 2 name: 'yanxi', 3 props: { name: 'qiu' }, 4} 5const obj2 = JSON.parse(JSON.stringify(obj)) 6obj2.props.name = '11111' 7obj 8//{name: "yanxi", props: {name: "qiu"}}
这种方式非常简单,但 JSON 的局限性较大。
局限性如下:
1、不支持函数,会自动忽略
2、不支持 undefined,JSON 天然不支持
3、不支持环状引用(即引用自身)会报错

4、不支持 Date,会转成字符串
5、不支持 symbol,JSON 天然不支持

我们可以看到 JSON.parse(JSON.stringify())虽然能够深拷贝一个对象,但是存在很大的局限性,对于复杂的对象就不适用了。因此,我们需要采用另外的方式来实现深拷贝,也就是通过递归的方式手动实现深拷贝。
递归深拷贝
JS 中存在七种类型:number、string、boolean、symbol、null、undefined、object
其中除了 object 属于引用类型,其余都是简单数据类型,所以我们主要要对 object 的数据类型进行分辨,其余的简单数据类型都只要直接返回即可。
知道了深拷贝的概念,有了深拷贝的思路
下面我们从 0 开始,手撸一个深拷贝
简单数据类型深拷贝
简单数据是不可变的
js1let a = 1 2let b = a 3b = 2 4a //1
上面的代码只是改变了 b 的指向,并不影响原来的数据 a。
那么我们就可以直接实现对于简单数据类型的深拷贝函数
js1function deepClone(target) { 2 return target 3}
object 类型深拷贝
深拷贝中最重要的是解决 object 类型深拷贝的思路,基于这个复杂数据类型,我们需要对 Javascript 中的各种对象(数组对象,函数对象等)进行深拷贝的实现。
javascript1function cloneDeep(target) { 2 let result 3 if (typeof target === 'object') { 4 result = {} 5 for (let key in target) { 6 result[key] = cloneDeep(target[key]) 7 } 8 return result 9 } 10 return target 11}
上面的代码通过对类型的检测,如果发现传入的是一个 object 对象,那么就循环它的属性,并且通过递归的方式一一将 object 对象的属性打到新创建的空白对象中,并将其返回。
Array 类型的深拷贝
在对数组类型进行检测的时候,我们不能用 typeof 进行检测,因为会返回 object,这里我们需要使用的Object.prototype.toString.call(Array)或者instanceof关键字,为了方便,这里就使用 instanceof 关键字
javascript1function cloneDeep(target) { 2 let result 3 if (typeof target === 'object') { 4 if (target instanceof Array) { 5 result = [] 6 for (let key of target) { 7 result.push(cloneDeep(key)) 8 } 9 } else { 10 result = {} 11 for (let key in target) { 12 result[key] = cloneDeep(target[key]) 13 } 14 } 15 return result 16 } 17 return target 18}
函数深拷贝
函数需要怎么深拷贝呢?我们知道函数中的参数传递都是值传递,虽然目标函数会改变引用地址,但是函数已经把值作为参数传递进去了,所以我们直接返回调用函数的结果就可以了。
javascript1let fn=function (){return 123} 2cloneDeep(fn) / /值传递 3fn=function (){return 456} // 这里的函数改变了引用地址 4cloneDeep(fn)() //需要做到返回123
下面是实现代码,为了维持代码的结构,让简单数据类型和复杂数据类型做分离,我使用 instanceof 检测 target 类型,当检测函数时才使用 typeof 关键字
javascript1function cloneDeep(target) { 2 let result 3 if (target instanceof Object) { 4 if (target instanceof Array) { 5 result = [] 6 for (let key of target) { 7 result.push(cloneDeep(key)) 8 } 9 } else if (typeof target === 'function') { 10 result = function(...rest) { 11 return target(...rest) 12 } 13 } else { 14 result = {} 15 for (let key in target) { 16 result[key] = cloneDeep(target[key]) 17 } 18 } 19 return result 20 } 21 return target 22}
还有更多的数据对象如果想要实现深拷贝,只要新增类型判断即可,这里就不再扩展了。
结束~
enjoy!