JS深拷贝

158 阅读4分钟

浅拷贝

值是基本类型,拷贝的就是基本类型的值

是引用类型,拷贝的就是内存地址,这时就会出现同时改变

let obj={
  name:'张三',
  age:18,
  '身高':180,
  '体重':50,
  cards:[1,324,354,546,567,567,345]
}

let test=obj

test['age']=20
console.log('obj',obj)
console.log('test',test)

/*
obj 
{name: "张三", age: 20, 身高: 180, 体重: 50, cards: Array(7)}
test 
{name: "张三", age: 20, 身高: 180, 体重: 50, cards: Array(7)}
*/

浅拷贝的方法还有Object.assign()es6展开运算符...,针对数组的还有Array.prototype.concat(),Array.prototype.slice()

let obj={
  name:'张三',
  age:18,
  '身高':180,
  '体重':50,
  cards:[1,324,354,546,567,567,345]
}
let ary=[234,345,45345,7567,568]


let test1=Object.assign({},obj)
let test2={...obj}
let ary1=ary.concat()
let ary2=ary.slice()

test1.age=20
test2.身高=200
console.log(obj)
console.log(test1);
console.log(test2);
console.log('-----------------')
ary1[0]=100
ary2[0]=1000
console.log(ary)
console.log(ary1);
console.log(ary2);

/*
{name: "张三", age: 18, 身高: 180, 体重: 50, cards: Array(7)}
{name: "张三", age: 20, 身高: 180, 体重: 50, cards: Array(7)}
{name: "张三", age: 18, 身高: 200, 体重: 50, cards: Array(7)}
----------------- 
(5) [234, 345, 45345, 7567, 568]
(5) [100, 345, 45345, 7567, 568]
(5) [1000, 345, 45345, 7567, 568]
*/

深拷贝

将一个对象从内存中完整拷贝一份,开辟一个新区域存放,即复制对象本身,互不影响

查阅一番,看到最多最简单的是

JSON.parse(JSON.stringify())

试一下

let obj={
   name:'张三',
   age:18,
  '身高':180,
  '体重':50,
  cards:[1,324,354,546,567,567,345]
}

let test=JSON.parse(JSON.stringify(obj))

test['age']=20
delete test['体重']
test.cards[0]=100
console.log('obj',obj)
console.log('test',test)


/*
obj 
{name: "张三", age: 18, 身高: 180, 体重: 50, cards: Array(7)}
name: "张三"
age: 18
身高: 180
体重: 50
cards: Array(7)
0: 1
1: 324
2: 354
3: 546
4: 567
5: 567
6: 345
test 
{name: "张三", age: 20, 身高: 180, cards: Array(7)}
name: "张三"
age: 20
身高: 180
cards: Array(7)
0: 100
1: 324
2: 354
3: 546
4: 567
5: 567
6: 345
*/

成功了!但是这个是不合格的,于是我们自己实现一个

const deepClone=(target)=>{
  if(typeof target==='object'){
    let result={}
    for(const key in target){
       result[key]=deepClone(target[key])
    }
    return result
  }else{
    return target
  }
}

/*


obj 
{name: "张三", age: 18, 身高: 180, 体重: 50, cards: Array(7)}
name: "张三"
age: 18
身高: 180
体重: 50
cards: Array(7)
0: 1
1: 324
2: 354
3: 546
4: 567
5: 567
6: 345
test 
{name: "张三", age: 20, 身高: 180, cards: Object}
name: "张三"
age: 20
身高: 180
cards: Object
0: 100
1: 324
2: 354
3: 546
4: 567
5: 567
6: 345
*/

但是这样没有考虑数组,结果是数组虽然被拷贝了,但是被当作object保存了(for...in遍历出的key,所以看起来数据没有问题),数组直接typeof数组的结果也是'object',所以得另外判断

const deepClone=(target)=>{
  if(typeof target==='object'){
    let result=Array.isArray(target)?[]:{}
    for(const key in target){
       result[key]=deepClone(target[key])
    }
    return result
  }else{
    return target
  }
}

循环引用


如果对象存在循环引用,需要另做处理

const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
target.target = target;

我们需要额外开辟一个存储空间,来存储当前对象和拷贝对象的关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝

const deepClone=(target,map=new Map())=>{
  if(typeof target==='object'){
    let result=Array.isArray(target)?[]:{}
    if(map.get(target)){
      return map.get(target)
    }
    map.set(target,result)
    for(const key in target){
       result[key]=deepClone(target[key],map)
    }
    return result
  }else{
    return target
  }
}

这样就能避免对自身的循环引用。

但是Map是强引用,当拷贝对象非常庞大时会造成内存的巨大消耗,可以用WeakMap代替

此外还可以继续对日期类型、正则、函数进行判断


参考