深拷贝与浅拷贝

在平时的开发中使用深拷贝与浅拷贝的场景还是挺多的,比如从 api 接口中获取到请求的结果后,我们通常将请求结果通过浅拷贝的形式赋值给一个对象或数组。

浅拷贝

假定我们有一个这样的需求,给定一个对象 obj,需要将对象 obj 分别赋值给 clone1,clone2,代码实现如下

let obj = {
  a: 123
}

let clone1 = obj
let clone2 = obj

当将 clone1.a 的属性值改成 456 时,会发现 clone2.a 的属性值和 obj.a 的属性值都变成了 456,这是因为当使用等于号直接赋值时,clone1,clone2 中实际上保存都是对象 obj 的地址,当修改 clone1 对象中 a 属性的值时,实际上修改的是 obj.a 的值

let obj = {
  a: 123
};

let clone1 = obj;
let clone2 = obj;

clone1.a = 456;

console.log('obj.a:', obj.a);
console.log('clone1.a:', clone1.a);
console.log('clone2.a:', clone2.a);

执行结果

使用对象展开运算符实现浅拷贝

我们将上面的代码改成使用对象展开运算符实现的浅拷贝的形式后,发现当改变 clone1.a 的值后,obj.a,clone2.a 的值都没发生变化,因为改成浅拷贝的形式实际上只是对象的拷贝,没有拷贝地址

let obj = {
  a: 123
};

let clone1 = {
  ...obj
};

let clone2 = {
  ...obj
};

clone1.a = 456;

console.log('obj.a:', obj.a);
console.log('clone1.a:', clone1.a);
console.log('clone2.a:', clone2.a);

执行结果

使用 Object.assign 实现浅拷贝

let obj = {
  a: 123
};

let clone1 = Object.assign({}, obj);
let clone2 = Object.assign({}, obj);

clone1.a = 456;

console.log('obj.a:', obj.a);
console.log('clone1.a:', clone1.a);
console.log('clone2.a:', clone2.a);

执行结果

使用 Object.key 实现浅拷贝

let obj = {
  a: 123
};

let clone1 = Object.keys(obj).reduce((res, cur) => ({...res, [cur]: obj[cur]}), {})
let clone2 = Object.keys(obj).reduce((res, cur) => ({...res, [cur]: obj[cur]}), {})

clone1.a = 456;

console.log('obj.a:', obj.a);
console.log('clone1.a:', clone1.a);
console.log('clone2.a:', clone2.a);

执行结果

使用 for 循环遍历对象的方式实现浅拷贝

let obj = {
  a: 123
};

let clone1 = {};
let clone2 = {};

for(let key in obj) {
  clone1[key] = obj[key]
}

for(let key in obj) {
  clone2[key] = obj[key]
}

clone1.a = 456;

console.log('obj.a:', obj.a);
console.log('clone1.a:', clone1.a);
console.log('clone2.a:', clone2.a);

执行结果

深拷贝

在使用深拷贝之前,我们也假定一个场景,例如要对下面这个比较复杂的对象进行拷贝,拷贝完后修改拷贝后对象下子对象中的一个属性,看看是否会影响待拷贝的对象呢,这个对象比较复杂,对象的属性即有对象,也有数组,还有 Set 类型的数据

const obj = {
  a: 1,
  b: 'abc',
  c: {
    c1: 111
  },
  d: [1, 2, {
    d1: 111
  }],
  e: new Set([{e1: 111}])
};

let clone1 = {
  ...obj
};

let clone2 = {
  ...obj
};

clone1.c.c1 = 222;

console.log('obj.c.c1:', obj.c.c1);
console.log('clone1.c.c1:', clone1.c.c1);
console.log('clone2.c.c1:', clone2.c.c1);

执行结果

通过执行结果可知在比较复杂的对象中采用浅拷贝的方式是行不通的,因为拷贝属性中的对象时,会同时拷贝指向对象的指针(内存地址),当修改子对象中的属性值时,实际上是修改原对象下子对象的属性

采用深拷贝的方式可以解决上面的问题

const obj = {
  a: 1,
  b: 'abc',
  c: {
    c1: 111
  },
  d: [1, 2, {
    d1: 111
  }],
  e: new Set([{e1: 111}]),
};

// 用于实现深拷贝
function deepClone(obj) {

  // 获得 obj 的数据类型
  const type = Object.prototype.toString.call(obj);

  switch(type) {

    // 如果 obj 是对象类型
    case '[object Object]': {
      const cloneObj = {};

      // 遍历对象
      for(let key in obj) {
        cloneObj[key] = deepClone(obj[key]);
      }

      return cloneObj;
    }

    // 如果 obj 是数组类型
    case '[object Array]': {
      return obj.map(item => {
        return deepClone(item);
      })
    }

    // 如果 obj 是 Set 类型
    case '[object Set]': {
      const cloneObj = new Set();

      obj.forEach(item => {
        cloneObj.add(deepClone(item));
      });

      return cloneObj;
    }
    default:
      return obj;
  }
}

let clone1 = deepClone(obj);
let clone2 = deepClone(obj);
clone1.c.c1 = 222;
clone1.d = [3, 4, {d1: 222}];
clone1.e.add(1234);


console.log('obj.c.c1:', obj.c.c1);
console.log('clone1.c.c1:', clone1.c.c1);
console.log('clone2.c.c1:', clone2.c.c1);
console.log('**************************');
console.log('obj.d:', obj.d);
console.log('clone1.d:', clone1.d);
console.log('clone2.d:', clone2.d);
console.log('**************************');
console.log('obj.e:', obj.e);
console.log('clone1.e:', clone1.e);
console.log('clone2.e:', clone2.e);

执行结果

通过上面的执行结果可知,当我们将 obj 对象通过采用深拷贝的方式拷贝后,当修改 obj 对象下的属性时,不管属性是 Number 类型、Object 类型、Array 类型、还是 Set 类型,拷贝后的对象属性值都没发生变化

参考链接

meishadevs欢迎任何形式的转载,但请务必注明出处,尊重他人劳动成果。
转载请注明: 【文章转载自meishadevs:深拷贝与浅拷贝

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器