手写深拷贝
18888628835 opened this issue · 0 comments
手写深拷贝
深拷贝是什么
在JS中,所有的拷贝API都是浅拷贝,比如数组的拷贝,我们一般使用Array.prototype.slice
来拷贝一个数组,但是对于嵌套数组,就会只拷贝其中的引用。
const arr=[[1,2,3],[4,5,6]]
const arr2=arr.slice()
arr2[0].push(666)
console.log(arr) //[[1, 2, 3, 666], [4, 5, 6]]
上面的arr2是拷贝后的数组,arr2改变了同样会影响到arr的值,所以这就不是深拷贝。
官方解释是这样的
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
我所理解的深拷贝,就是当我克隆一个东西出来之后,跟原来的完全不相交。
序列化与反序列化的深拷贝
在工作中,我们一般都会使用序列化进行深拷贝,就是采用JSON.stringify
和JSON.parse
来进行深拷贝
const obj={
name:'yanxi',
props:{name:'qiu'}
}
const obj2=JSON.parse(JSON.stringify(obj))
obj2.props.name='11111'
obj
//{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开始,手撸一个深拷贝
简单数据类型深拷贝
简单数据是不可变的
let a=1
let b=a
b=2
a //1
上面的代码只是改变了b的指向,并不影响原来的数据a。
那么我们就可以直接实现对于简单数据类型的深拷贝函数
function deepClone(target){
return target
}
object 类型深拷贝
深拷贝中最重要的是解决 object 类型深拷贝的思路,基于这个复杂数据类型,我们需要对Javascript 中的各种对象(数组对象,函数对象等)进行深拷贝的实现。
function cloneDeep(target) {
let result
if (typeof target === 'object') {
result = {}
for (let key in target) {
result[key] = cloneDeep(target[key])
}
return result
}
return target
}
上面的代码通过对类型的检测,如果发现传入的是一个 object 对象,那么就循环它的属性,并且通过递归的方式一一将 object 对象的属性打到新创建的空白对象中,并将其返回。
Array 类型的深拷贝
在对数组类型进行检测的时候,我们不能用 typeof 进行检测,因为会返回 object
,这里我们需要使用的Object.prototype.toString.call(Array)
或者instanceof
关键字,为了方便,这里就使用 instanceof 关键字
function cloneDeep(target) {
let result
if (typeof target === 'object') {
if (target instanceof Array) {
result = []
for (let key of target) {
result.push(cloneDeep(key))
}
} else {
result = {}
for (let key in target) {
result[key] = cloneDeep(target[key])
}
}
return result
}
return target
}
函数深拷贝
函数需要怎么深拷贝呢?我们知道函数中的参数传递都是值传递,虽然目标函数会改变引用地址,但是函数已经把值作为参数传递进去了,所以我们直接返回调用函数的结果就可以了。
let fn=function (){return 123}
cloneDeep(fn) / /值传递
fn=function (){return 456} // 这里的函数改变了引用地址
cloneDeep(fn)() //需要做到返回123
下面是实现代码,为了维持代码的结构,让简单数据类型和复杂数据类型做分离,我使用 instanceof 检测 target 类型,当检测函数时才使用 typeof 关键字
function cloneDeep(target) {
let result
if (target instanceof Object) {
if (target instanceof Array) {
result = []
for (let key of target) {
result.push(cloneDeep(key))
}
} else if (typeof target === 'function') {
result = function(...rest) {
return target(...rest)
}
} else {
result = {}
for (let key in target) {
result[key] = cloneDeep(target[key])
}
}
return result
}
return target
}
还有更多的数据对象如果想要实现深拷贝,只要新增类型判断即可,这里就不再扩展了。
结束~
enjoy!