我们知道JavaScript的数据类型分为基本数据类型和引用数据类型。对于基本数据类型,不存在深浅拷贝的问题,只要是复制,都相当于是深拷贝。而对于引用数据类型,一般的拷贝只是做到了引用地址的拷贝,而不是深层次的拷贝,所以下面我们所有的讨论都是基于引用数据类型的。

浅拷贝

浅拷贝很简单,=的操作就是。

  1. const originArray = [1,2,3,4,5];
  2. const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
  3. const cloneArray = originArray;
  4. const cloneObj = originObj;
  5. console.log(cloneArray); // [1,2,3,4,5]
  6. console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}
  7. cloneArray.push(6);
  8. cloneObj.a = {aa:'aa'};
  9. console.log(cloneArray); // [1,2,3,4,5,6]
  10. console.log(originArray); // [1,2,3,4,5,6]
  11. console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
  12. console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

结果可以很清晰的看出来拷贝的值和原值是会相互影响的。

深拷贝

深拷贝就是对目标的完全拷贝,不只是对象引用额拷贝,而是值的拷贝。

实现了深拷贝就使两个对象完全无关,各自的变化不会相互影响。

实现深拷贝的方法有

  1. 利用JSON对象的parse和stringify
  2. 利用递归实现每一层重新创建对象赋值

    JSON.stringify/parse的方法

    这个JSON方法能实现原理很简单,stringify相当于是把对象变成了字符串,而字符串是基本数据类型,=号的操作就是深拷贝了,再利用parse重新解析为对象。 ```javascript 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’}};

  1. 很容易我们就实现了深拷贝,但是越简单我们越要警惕,为什么这个这么简单,还要去研究其他的方法呢?下面我们就来看一种情况
  2. ```javascript
  3. const originObj = {
  4. name:'axuebin',
  5. sayHello:function(){
  6. console.log('Hello World');
  7. }
  8. }
  9. console.log(originObj); // {name: "axuebin", sayHello: ƒ}
  10. const cloneObj = JSON.parse(JSON.stringify(originObj));
  11. console.log(cloneObj); // {name: "axuebin"}

元对象的sayHello是一个函数,对于函数,这个方法不回去转化,mdn上有一段话解释了这个问题,大概意思就是:
undefined,function,symbol会在转换过程中被忽略。
所以,虽然很简单的实现了深拷贝,但是缺点也是很明显的,所以我们需要另一种方法来完美的实现深拷贝。

递归实现深拷贝

递归的思路很简单,就是遍历对象,如果值是一个基本数据类型,那么就直接拷贝,如果是一个引用数据类型,那么就利用递归拷贝,知道遍历所有值都是基本数据类型。

  1. function deepClone(source){
  2. const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  3. for(let keys in source){ // 遍历目标
  4. if(source.hasOwnProperty(keys)){
  5. if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
  6. targetObj[keys] = source[keys].constructor === Array ? [] : {};
  7. targetObj[keys] = deepClone(source[keys]);
  8. }else{ // 如果不是,就直接赋值
  9. targetObj[keys] = source[keys];
  10. }
  11. }
  12. }
  13. return targetObj;
  14. }
  15. const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
  16. const cloneObj = deepClone(originObj);
  17. console.log(cloneObj === originObj); // false
  18. cloneObj.a = 'aa';
  19. cloneObj.c = [1,1,1];
  20. cloneObj.d.dd = 'doubled';
  21. console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
  22. console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
  23. const originObj = {
  24. name:'axuebin',
  25. sayHello:function(){
  26. console.log('Hello World');
  27. }
  28. }
  29. console.log(originObj); // {name: "axuebin", sayHello: ƒ}
  30. const cloneObj = deepClone(originObj);
  31. console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}

搞定

JavaScript中的拷贝方法

在JavaScript中,我们经常会使用到一下方法,去进行复制对象的操作。比如数组的concat和slice方法,这两个方法不会修改原数组,而是返回一个新的数组。
另外还有es6的Object.assign和…展开运算符
它们是不是深拷贝的,很简单,试试就知道了。

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

结果很明显,对于第一层我们进行了深拷贝,而如果第二层第三层仍然是引用数据类型,那么我们得到的还是一个引用。

  1. //slice
  2. const originArray = [1,2,3,4,5];
  3. const cloneArray = originArray.slice();
  4. console.log(cloneArray === originArray); // false
  5. cloneArray.push(6); // [1,2,3,4,5,6]
  6. console.log(originArray); [1,2,3,4,5];
  7. const originArray = [1,[1,2,3],{a:1}];
  8. const cloneArray = originArray.slice();
  9. console.log(cloneArray === originArray); // false
  10. cloneArray[1].push(4);
  11. cloneArray[2].a = 2;
  12. console.log(originArray); // [1,[1,2,3,4],{a:2}]
  13. //Object.assign
  14. let obj = {a: 1, b: 2}
  15. let newObj = Object.assign({}, obj)
  16. // console.log(obj, newObj)
  17. newObj.a = 3
  18. console.log(obj, newObj) //{ a: 1, b: 2 } { a: 3, b: 2 }
  19. let obj2 = {a: 1, b: 2, c: {cc: 'cc'}}
  20. let newObj2 = Object.assign({}, obj2)
  21. newObj2.c.cc = 'ccc'
  22. console.log(obj2, newObj2)
  23. //{ a: 1, b: 2, c: { cc: 'ccc' } } { a: 1, b: 2, c: { cc: 'ccc' } }
  24. //...展开运算符
  25. const originArray = [1,2,3,4,5,[6,7,8]];
  26. const originObj = {a:1,b:{bb:1}};
  27. const cloneArray = [...originArray];
  28. cloneArray[0] = 0;
  29. cloneArray[5].push(9);
  30. console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]
  31. const cloneObj = {...originObj};
  32. cloneObj.a = 2;
  33. cloneObj.b.bb = 2;
  34. console.log(originObj); // {a:1,b:{bb:2}}

最终,我们得到的结果都一样,这些方法只是对第一层进行了深拷贝,如果第二层仍然是引用数据类型,就只是拷贝的值的引用。

总结

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