本文已参与「新人创作礼」活动,一起开启掘金创作之路。
🏡学习深拷贝函数
浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
深拷贝会使拷贝出来的对象与原对象完全脱离关系。
长期以往,实现深拷贝都有各个方法
1.JSON.parse(JSON.stringify());
这种方法是将一个对象序列化,然后有解析出来赋给一个新对象,简单实现了拷贝,但不足之处就是会跳过function
2.封装一个基本实现深拷贝的函数
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === 'function' || valueType === 'object')
}
function deepClone(originObj) {
//判断传入的originObj是否是一个对象类型
if (!isObject(originObj)) {
return originObj
}
const newObject = {}
for (const key in originObj) {
newObject[key] = deepClone(originObj[key])
}
return newObject
}
这种将一个确定为对象的对象for-in遍历,拿到key,当value是基本类型时,就会在newObj里同样加入这个属性,当value是对象类型,又会为这个对象深拷贝,最后将这个对象的深拷贝结果返回给最初的newObj的key值,这种递归调用,处理基本类型和引用类型。
顺利的话,我们的函数可以实现深拷贝了,但往往不顺利,因为对象里有各种类型值,需要进行特殊处理,例如function,array,symbol的key,symbol的value,以至于指向自身的值,这些情况统统需要考虑在内。
// // 判断是否是一个Set类型
if (originObj instanceof Set) {//用type判断出来是obj{}
return new Set([...originObj])
}
// 判断是否是一个Map类型
if (originObj instanceof Map) {//用type判断出来是obj{}
return new Map([...originObj])
}
// 判断如果是symbold的value,那么创建一个新的Symbol,
if (typeof originObj === "symbol") {
return Symbol(originObj.description)
}
//判断如果是函数类型,直接返回使用同一个函数
if (typeof originObj == "function") {
return originObj
}
//判断传入的originObj是否是一个对象类型
if (!isObject(originObj)) {
return originObj
}
if (map.has(originObj)) {
return map.get(originObj)
}
//判断传入的是数组还是对象
const newObject = Array.isArray(originObj) ? [] : {}
map.set(originObj, newObject) //设计map为了防止循环引用无线创建对象
for (const key in originObj) {//遍历不到symbol的key
newObject[key] = deepClone(originObj[key], map)
}
//对Symbol的key进行特殊处理
const symbolKeys = Object.getOwnPropertySymbols(originObj)
for (const sKey of symbolKeys) {
// const newSkey = Symbol(sKey.description)
// newObject[newSkey] = deepClone(originObj[sKey])
newObject[sKey] = deepClone(originObj[sKey], map)
}
return newObject
分析:
1.当为函数类型时,判断为函数类型,则直接返回这个函数,对newObj直接设计为newObj[key] = 返回的function,因为没必要对这个函数进行任何复制之类的操作
2.当为一个set类型时,则新建一个set将原来的set值展开到新创的set里并返回即可
3.当为map类型时同样新建,将值用扩展运算符展开到新建的map里,然后返回
4.因为for in 遍历不到Symbol类型的key,所以需要使用 Object.getOwnPropertySymbols(originObj)对这些属性进行处理创建到newObj里
需要解决循环引用问题,当有指向自身的属性如果还是按之前那样deepClone一遍,则这样无
穷无尽,正确的做法是,返回最初的newObj,当循环引用在自己的第一层即obj{info:
obj},当拿到属性info的值时,可以判断若为自身,则直接设置为newObj,但若info:obj出
现在自己属性里的深处即多个对象嵌套,在最深的对象即最深的递归调用里如何拿到最初的
newObj。于是每次判断是否value等于自身并不可靠。站在数据结构的思想上,需要对这种数
据保存,即创建一个weakMap,每次新建newObj时就进行原对象(obj)和新对象(newObj)
的保存,在每次准备进行clone之前,先判断本次要复制的对象是否在之前已经创建过,若创建
过则代表这是一个循环引用,直接取出这个对象之前创建的newObj返回即可。