01.关于深拷贝和浅拷贝的定义
js总共有7种数据类型 ,其中Number,String,Boolean,Null,Undefined,Symbol是值类型,Array,Object是引用类型
Symbol是es6中新加入的语法,用于表示独一无二的值。
引用类型在赋值变量的时候,赋值只会拷贝引用的地址,这种做法有好处也有坏处,好处就是赋值操作的效率只需要赋值一个地址,效率很高。缺点是,当我修改赋值的变量也会改变原来变量中的数据,因为这两个变量是同一个地址,修改的是同一片数据。
所以引用类型当我们需要拷贝一个新的变量的时候,需要自己实现深拷贝或者浅拷贝
浅拷贝就是拷贝的时候,对于引用类型只进行简单的赋值,也就是只拷贝一份地址,以后修改变量的时候改的是同一份内存区域的内容
深拷贝就是拷贝的时候,对于引用类型对地址指向的内容进行拷贝。
一般情况下,浅拷贝的方法就够用了。因为拷贝的东西越多,其实效率也就越低,深拷贝还是尽量避免比较好。
02.浅拷贝的代码实现
举个例子,实现卡池抽卡gacha的功能的时候,我们把每张卡的信息都用一个对象进行描述,把这些对象都放在一个数组里就形成了卡池gachaPool
每张卡都有一个抽取概率,卡池所有卡的抽卡概率和为1。这样就可以用一个随机数函数实现基本的抽卡模拟了,随机0-1之间的数字,看看结果落在哪个区间上就是哪张卡。抽到的卡我们用一个新数组resultList来存储。
如果直接把卡牌的对象push到resultList里 ,假设我们抽到卡以后需要修改抽到的卡对象上的一个数据,或者加上什么属性,就会影响到卡池里的卡的属性,这显然是不合理的。我们需要拷贝一个新的对象。
如果我们修改的数据不包括引用类型,那么只需要浅拷贝就可以。
下面就是我实现浅拷贝的代码:
shallowClone (sourceObj) {
const newObj = {}
for (const key in sourceObj) {
if (Object.hasOwnProperty.call(sourceObj, key)) {
newObj[key] = sourceObj[key]
}
}
return newObj
},
上面使用的遍历对象的方法是一种兼容性比较好的方法
for in 可以遍历对象自身的和继承(也就是原型链上的)的可枚举属性(不含 Symbol 属性),但是我们通常只需要遍历对象自身的属性,所以用hasOwnProperty就可以过滤出自身的属性。
上面的方式碰到数组和对象之类的类型的时候,也是普通的赋值(拷贝地址),所以你抽到的卡里面的对象和卡池列表里的gachaPool卡的数组类型和对象类型的数据是一样的。因为卡片目前没有对象或者数组类型的数据,所以这样的浅拷贝也能满足使用需求了。
03.深拷贝的实现
01.最简单版本
一个比较简单的方法是利用json序列化的api
JSON.parse(JSON.stringify())
这个方法的缺点是,不能拷贝函数,没有处理循环引用等。而且效率也比较低。
02.基础版本
我们需要对于常见的场景中Object和Array类型的数据,进行判断,对于这类数据,用递归的方法,对他们进行深拷贝。
function deepClone (target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}
for (const key in target) {
cloneTarget = deepClone(target[key])
}
return cloneTarget
} else {
return target
}
}
这个版本还存在着一些问题,比如用for in 进行遍历,所以不会遍历到symbol类型数据,也就是不会拷贝symbol类型数据,同时也没有对函数进行处理,还有没有解决循环引用的问题,还有一个问题,因为使用了递归,所以层数过深的时候,可能导致栈溢出。
不过已经在很多场景下满足使用需求了。
网友评论