javascript深浅拷贝【学习总结与记录】

对于基本数据类型的拷贝,并没有深浅拷贝的区别,所说的深浅拷贝都是对于引用数据类型而言的。

浅拷贝—只是复制引用,而未复制真正的值。会影响到原数据

const originArray=[1,2,3,4,5];
const originObj={a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneArray=originArray;
const cloneObj=originObj;

console.log(cloneArray);//[1, 2, 3, 4, 5]
console.log(originObj);//{a:'a',b:'b',c:Array[3],d:{dd:'dd'}}

cloneArray.push(6);
cloneObj.a={aa:'aa'};

console.log(cloneArray);//[1, 2, 3, 4, 5, 6]
console.log(originArray);//[1, 2, 3, 4, 5, 6]

console.log(cloneObj); //{a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); //[1, 2, 3, 4, 5, 6]

上面的代码是最简单的利用=赋值操作符实现了一个浅拷贝,可以清楚看到,随着cloneArray和cloneObj改变,originArray和originObj也随着发生了变化。

深拷贝—对目标完全拷贝,不像深拷贝那样只是复制了一层引用,连值也都复制了。
目的在于避免拷贝后数据对原数据产生影响

只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
目前实现深拷贝的方法不多,主要是两种:

  1. 利用JSON对象中的parse和stringify
  2. 利用递归来实现每一层都重新创建对象并赋值
    JSON.stringify是将一个javascript值转成一个JSON字符串。
    JSON.parse是将一个JSON字符串转成一个javascript值或对象
    就是javascript值和JSON字符串的相互转换。
const originArray=[1,2,3,4,5];
const cloneArray=JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray) //false

const originObj={a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj=JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj)  //false

cloneObj.a='aa';
cloneObj.c=[1,1,1];
cloneObj.d.dd='doubled';

console.log(cloneObj); //{a: "aa", b: "b",c: [1, 1, 1],d:{ dd: "doubled"}}
console.log(originObj) //{a: "a",b: "b",c: [1, 2, 3],d:{dd: "dd"}}

确实是深拷贝,也很方便,但是,这个方法只能适用于一些简单的情况,比如下面的一个对象就不适用:

const originObj={
  name:'axuebin',
  sayHello:function(){
    console.log('hello world');
  }  
}
console.log(originObj) //{name:"axuebin",sayHello:f}
const cloneObj=JSON.parse(JSON.stringify(originObj));
console.log(cloneObj) //{name: "axuebin"}

发现cloneObj中,有属性丢失了,是为什么呢?
原因:undefined,function,symbol会在转换过程中被忽略。
就是说,如果对象中含有一个函数时,就不能用这个方法进行深拷贝。

递归的方法—对每一层的数据实现一次创建对象->对象赋值的操作

function deepClone(source){
  const targetObj=source.construtor===Array ? [] : {};//判断复制的是数组还是对象
  for(let keys in source){ //遍历目标
    if(source.hasOwnProperty(keys)){
    if(source[keys] && typeof source[keys] === 'object'){ //如果值是对象就递归
      targetObj[keys]=source[keys].construtor===Array?[]:{};
      targetObj[keys]=deepClone(source[keys])
    }else{ //如果不是,就直接赋值
      targetObj[keys]=source[keys]
    }
    }
  }
  return targetObj;
 }

测试

const originObj={a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj=deepClone(originObj);
console.log(cloneObj === originObj) //false

cloneObj.a='aa';
cloneObj.c=[1,1,1];
cloneObj.d.dd='doubled'

console.log(cloneObj) //{a: "aa",b: "b",c: [1, 1, 1],d:{dd: "doubled"}}
console.log(originObj) //{a: "a",b: "b",c: [1, 2, 3],d:{dd: "dd"}}

带有函数的

const originObj={
  name:'axuebin',
  sayHello:function(){
    console.log('hello world')
  }
}
console.log(originObj);//{name: "axuebin",sayHello: f}
const cloneObj=deepClone(originObj);
console.log(cloneObj);//{name: "axuebin",sayHello: f}

也可以搞定,但是还没有完。

js中的拷贝方法

js中数组有两个方法concat和slice是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。
同时,ES6中引入了Object.assgn方法和…展开运算符也能实现对对象的拷贝。
那它们是深拷贝还是浅拷贝呢?
concat—该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
看着意思像是深拷贝,试试:

const originArray=[1,2,3,4,5];
const cloneArray=originArray.concat();

console.log(cloneArray===originArray) //false
cloneArray.push(6);
console.log(cloneArray) //[1, 2, 3, 4, 5, 6]
console.log(originArray) //[1, 2, 3, 4, 5]

看起来是深拷贝的。
再来考虑一个问题,如果这个对象是多层的,会怎样

const originArray=[1,[1,2,3],{a:1}];
const cloneArray=originArray.concat();
console.log(cloneArray===originArray) //false
cloneArray[1].push(4);
cloneArray[2].a=2;
console.log(originArray) //[1,[1,2,3,4],{a:2}]

originArray中含有数组[1,2,3]和对象{a:1},如果直接修改数组和对象,不会影响originArray,但修改数组[1,2,3]或对象{a:1}时,发现originArray也发生了变化。
结论:concat只是对数组的第一层进行深拷贝。

slice—解释中说的是a shallow copy
但是,并不是

const originArray=[1,2,3,4,5];
const cloneArray=originArray.slice();
console.log(cloneArray===originArray); //false

cloneArray.push(6);
console.log(cloneArray) //[1, 2, 3, 4, 5, 6]
console.log(originArray) //[1, 2, 3, 4, 5]

同样的,试试多层的数组

const originArray=[1,[1,2,3],{a:1}];
const cloneArray=originArray.slice();
console.log(cloneArray === originArray)
cloneArray[1].push(2);
cloneArray[2].a=2;
console.log(originArray); //[1,[1,2,3,4],{a:2}]

结果和concat是一样的。
结论:slice只是对数组的第一层进行深拷贝

Object.assign()—拷贝的是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

可以复制以上代码进行测试

…展开运算符—实现的是对对象第一层的深拷贝,后面的只是拷贝的引用值

const originArray=[1,2,3,4,5,[6,7,8]];
const originObj={a:1,b:{bb:1}};

const cloneArray=[...originArray];
cloneArray[0]=0;
cloneArray[5].push(9);
console.log(originArray) //[1, 2, 3, 4, 5, [6, 7, 8, 9]]

const cloneObj={...originObj};
cloneObj.a=2;
cloneObj.b.bb=2;
console.log(originObj) //{a:1,b:{bb:2}}

会有一种情况,就是对目标对象的第一层进行深拷贝,然后后面的是浅拷贝,可以称作"首层浅拷贝"
可以实现一个函数:

function shallowClone(source){
  const targetObj=source.constructor===Array ? [] : {}
  for(let keys in source){
    if(source.hasOwnProperty(keys)){
      targetObj[keys]=source[keys];
    }
  }
  return targetObj;
}

测试

const originObj={a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj=shallowClone(originObj);
console.log(cloneObj===originObj) //false
cloneObj.a='aa';
cloneObj.c=[1,1,1];
cloneObj.d.dd='surprise';

经过上面的修改cloneObj肯定是{a: “aa”,b: “b”,c: [1, 1, 1],d:{dd: “surprise”}}了,那originObj呢?上面验证过了cloneObj===originObj是false,说明这两个对象引用地址不同,那应该就是修改了cloneObj并不影响originObj。

console.log(cloneObj) //{a: "aa",b: "b",c: [1, 1, 1],d:{dd: "surprise"}}
console.log(originObj) //{a: "a",b: "b",c: [1, 2, 3],d:{dd: "surprise"}}

发生了什么?!!!
originObj中关于a和c都没有被影响,但是d中的一个对象被修改了,说好的深拷贝呢?不是引用地址都不一样了吗?
原因:
从shallowClone的代码中,我们只对第一层的目标进行了深拷贝,而第二层开始的目标我们直接利用=赋值操作符进行拷贝的。
所以,第二层的目标都只是复制了一个引用,也就是浅拷贝。

总结

  1. 赋值运算符=实现的是浅拷贝,只拷贝对象的引用值
  2. js中数组和对象自带的拷贝方法都是首层浅拷贝
  3. JSON.stringify实现的是深拷贝,但是对目标对象有要求
  4. 若想真正意义上的深拷贝,请递归

应用场景

js拷贝大多会在 数据保存,数据比对,数据同步 时出现,所以,当在这些场景的时候,记得里面隐藏有一个数据深浅拷贝的问题。
需要根据使用的场景进行使用,首先要有无必要深拷贝,其次是数据类型,是否直接使用JSON的API其实就可以。

js深浅拷贝在日常开发中使用频率还是较高的,其中考察的知识点在于:

  1. 是否遇到过深浅拷贝的问题,里面有什么坑
  2. 是否了解js的数据类型,数据在计算机中的存储机制
  3. 是否了解数组,对象的一些常用API
  4. jquery,lodash,underscore的相关工具函数使用

浅拷贝不是直接赋值,浅拷贝新建了一个对象,然后将源对象的属性都一一复制过来,复制的是值,而不是引用。对象都是按地址引用进行访问的,浅拷贝的复制只复制了第一层的属性,并没有递归将所有的值都复制过来,所以,操作拷贝数据,对原数据产生了影响。

数组浅拷贝 – slice ,concat ,遍历
对象浅拷贝 – Object.assign , …扩展运算符
深拷贝 – JSON正反序列号 ,递归

// 深拷贝-JSON正反序列化
function deepClone(origin){
  return JSON.parse(JSON.stringify(arr))
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值