JS-浅拷贝与深拷贝

91 阅读3分钟

浅拷贝

浅拷贝出现的前提:引用数据类型(如对象和数组)。

对象深拷贝方案:

1. JSON转换:

代码:

const obj1 = {
  a: 1,
  b: 2,
  c: undefined,
  arr: [1, 2, 3],
  fun: () => { }
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 3;
obj1.arr[0] = 4;
console.log(obj1);
console.log(obj2);

输出结果:

{ a: 3, b: 2, c: undefined, arr: [ 4, 2, 3 ], fun: [Function: fun] }
{ a: 1, b: 2, arr: [ 1, 2, 3 ] }

可以看到在obj1中修改的数据或者是数组都不影响obj2的值。

缺点:当数据类型为function或数据的值为undefined的情况下无法复制。

2. Object.assign:

代码:

const obj1 = {
  a: 1,
  b: 2,
  c: undefined,
  arr: [1, 2, 3],
  fun: () => { }
};
const obj2 = Object.assign({}, obj1);
obj1.a = 3;
obj1.arr[0] = 4;
console.log(obj1);
console.log(obj2);

输出结果:

{ a: 3, b: 2, c: undefined, arr: [ 4, 2, 3 ], fun: [Function: fun] }
{ a: 1, b: 2, c: undefined, arr: [ 4, 2, 3 ], fun: [Function: fun] }

可以看到在obj1中修改的数据不影响obj2的值,同时也能拷贝值为undefined和数据类型为function的属性。

缺点:只能深拷贝一级属性,二级以上属性(引用类型)就是浅拷贝。

3. 扩展运算符:

代码:

const obj1 = {
  a: 1,
  b: 2,
  c: undefined,
  arr: [1, 2, 3],
  fun: () => { }
};
const obj2 = { ...obj1 };
obj1.a = 3;
obj1.arr[0] = 4;
console.log(obj1);
console.log(obj2);

输出结果:

{ a: 3, b: 2, c: undefined, arr: [ 4, 2, 3 ], fun: [Function: fun] }
{ a: 1, b: 2, c: undefined, arr: [ 4, 2, 3 ], fun: [Function: fun] }

该方法与Object.assign效果一样,也有同样的缺点。

4. 递归:

代码:

function cloneDeep(data) {
  const newData = Array.isArray(data) ? [] : {};
  for (let key in data) {
    if (data[key] && typeof data[key] === "object") {
      newData[key] = cloneDeep(data[key]);
    } else {
      newData[key] = data[key];
    }
  }
  return newData;
}

const obj1 = {
  a: 1,
  b: 2,
  c: undefined,
  arr: [1, 2, 3],
  fun: () => { }
};
const obj2 = cloneDeep(obj1);
obj1.a = 3;
obj1.arr[0] = 4;
console.log(obj1);
console.log(obj2);

输出结果:

{ a: 3, b: 2, c: undefined, arr: [ 4, 2, 3 ], fun: [Function: fun] }
{ a: 1, b: 2, c: undefined, arr: [ 1, 2, 3 ], fun: [Function: fun] }

可以看到在obj1中修改的任何数据都不影响obj2的值,同时也能拷贝值为undefined和数据类型为function的属性,因此该方案是一个近乎完美的方案。

缺点:唯一的缺点应该只是相对比较麻烦。

数组深拷贝方案

1. JSON转换:

代码:

const arr1 = [1, 2, 3, 4, undefined, { b: undefined, fun: () => { } }];
const arr2 = JSON.parse(JSON.stringify(arr1));
arr1[0] = 5;
console.log(arr1);
console.log(arr2);

输出结果:

[ 5, 2, 3, 4, undefined, { b: undefined, fun: [Function: fun] } ]
[ 1, 2, 3, 4, null, {} ]

该方法与在对象时的情况一致,无法复制据类型为function或数据的值为undefined的数据。

2. Object.assign:

代码:

const arr1 = [1, 2, 3, 4, undefined, { b: undefined, fun: () => { } }];
const arr2 = Object.assign([], arr1);
arr1[0] = 5;
arr1[5].b = 2;
console.log(arr1);
console.log(arr2);

输出结果:

[ 5, 2, 3, 4, undefined, { b: 2, fun: [Function: fun] } ]
[ 1, 2, 3, 4, undefined, { b: 2, fun: [Function: fun] } ]

该方法与在对象时的情况一致,只能深拷贝一级属性,二级以上属性(引用类型)就是浅拷贝。

注:拓展运算符.slice().concat()也同理。

3. 递归:

代码:(要注意setmap)

const isMap = (data) => {
  return data instanceof Map;
};
const isSet = (data) => {
  return data instanceof Set;
};
const isReferenceType = (data) => {
  return data && typeof data === "object";
};
const cloneDeep = (data) => {
  if (!isReferenceType(data)) return;

  let newData;
  if (isMap(data)) {
    newData = new Map();
    for (let [key, value] of data) {
      if (isReferenceType(value)) {
        newData.set(key, cloneDeep(value));
      } else {
        newData.set(key, value);
      }
    }
  } else if (isSet(data)) {
    newData = new Set();
    for (let value of data) {
      if (isReferenceType(value)) {
        newData.add(cloneDeep(value));
      } else {
        newData.add(value);
      }
    }
  } else {
    newData = Array.isArray(data) ? [] : {};
    for (let key in data) {
      if (isReferenceType(data[key])) {
        newData[key] = cloneDeep(data[key]);
      } else {
        newData[key] = data[key];
      }
    }
  }
  return newData;
};

const obj1 = {
  a: 1,
  b: undefined,
  c: null,
  arr: [1, 2, 3],
  fun: function () {
    console.log(this.a);
  },
  set: new Set([1, 2, 3]),
  map: new Map([
    ["a", 1],
    ["b", 2],
  ]),
};
const obj2 = cloneDeep(obj1);
obj2.a = 3;
obj2.arr[0] = 4;
obj2.set.delete(2);
obj2.set.add(4);
obj2.map.set("a", 3);
console.log(obj1);
console.log(obj2);

输出结果:

{
  a: 1,
  b: undefined,
  c: null,
  arr: [ 1, 2, 3 ],
  fun: [Function: fun],
  set: Set(3) { 1, 2, 3 },
  map: Map(2) { 'a' => 1, 'b' => 2 }
}
{
  a: 3,
  b: undefined,
  c: null,
  arr: [ 4, 2, 3 ],
  fun: [Function: fun],
  set: Set(3) { 1, 3, 4 },
  map: Map(2) { 'a' => 3, 'b' => 2 }
}

该方法的函数定义和使用与对象的一致,也是数组深拷贝的完美的解决方案。