JavaScript之深拷贝浅拷贝

220 阅读3分钟

JavaScript的数据类型分为基本数据类型引用数据类型,浅拷贝和深拷贝都是对于引用类型而言的。

浅拷贝

浅拷贝只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。

const arr1 = [1,2,3,4,5];
const arr2 = arr1;
console.log(arr2); // [1,2,3,4,5]
arr2.push(6);
console.log(arr1); // [1,2,3,4,5,6]
console.log(arr2); // [1,2,3,4,5,6]

最简单的利用 = 赋值操作符实现浅拷贝。

深拷贝

深拷贝是对目标的完全拷贝,连值也复制了,彼此相互独立。

实现深拷贝的方法:

  • 利用 JSON 对象中的 parse 和 stringify
  • 利用递归来实现每一层都重新创建对象并赋值

JSON.stringify/parse

  • JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串。
  • JSON.parse 是将一个 JSON 字符串转成一个 JavaScript 值或对象。
const arr1 = [1,2,3,4,5];
const arr2 = JSON.parse(JSON.stringify(arr1));
console.log(arr1 === arr2); // false

此方法适用于一些简单情况,如果对象中含有一个函数时,转换过程会被丢失。

递归

递归就是对每一层的数据都实现一次创建对象和对象赋值的操作。

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

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

js中的方法

concat和slice

concat方法可以连接两个或者更多的数组,返回一个新数组,但是它不会修改已存在的数组。

const arr1 = [1,2,3,4,5];
const arr2 = [1,[1,2,3],{a:1}];
const arr11 = arr1.concat();
const arr22 = arr2.concat();

console.log(arr1 === arr11); // false
console.log(arr2 === arr22); // false

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

arr22[1].push(4);
arr22[2].a = 2; 
console.log(arr2); // [1,[1,2,3,4],{a:2}]
console.log(arr22); // [1,[1,2,3,4],{a:2}]

concat 只是对数组的第一层进行深拷贝,修改时不会改变原值。如果对象是多层的,修改时会影响原值。

slice的结果和concat一样。

assign

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

...

es6中的...展开运算符,实现的是对象第一层的深拷贝,后面的只是拷贝的引用值。 结果类似于concat和slice。

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

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

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

所以

=赋值实现浅拷贝,JSON.stringify/parse实现简单的深拷贝(对象中不包括函数),递归可以实现完全的深拷贝。

js中数组和对象自带的方法都是首层浅拷贝,只实现第一层的深拷贝。