您的位置:

JS对象的深拷贝与浅拷贝

一、深拷贝与浅拷贝的概念

在进行JavaScript编程过程中,经常会涉及到对象的拷贝操作。对象的拷贝分为浅拷贝和深拷贝两种方式。

浅拷贝是指将一个对象复制到另一个对象,产生一个新的对象,新对象和原对象之间的基本类型数据相互独立,而对于引用类型的数据,依然指向原对象的存储空间。

深拷贝是指将源对象的值逐个复制到一个新的对象上,并且递归地将引用类型的数据也逐个复制到新对象上。这样,新对象和源对象就互相独立,互不影响。

二、浅拷贝的实现方式

浅拷贝可以通过Object.assign()或展开运算符(...)来实现。

//使用Object.assign()实现浅拷贝
let obj1 = {a: 1, b: 2};
let obj2 = Object.assign({}, obj1);
console.log(obj2); //{a: 1, b: 2}

//使用展开运算符实现浅拷贝
let obj1 = {a: 1, b: 2};
let obj2 = {...obj1};
console.log(obj2); //{a: 1, b: 2}

值得注意的是,浅拷贝只会复制原对象的第一层数据,如果原对象的某个属性是引用类型,那么新对象将使用同一个引用类型数据。

let obj1 = {a: 1, b: [1,2,3]};
let obj2 = {...obj1};
obj2.b.push(4);
console.log(obj1); //{a: 1, b: [1,2,3,4]}
console.log(obj2); //{a: 1, b: [1,2,3,4]}

三、深拷贝的实现方式

实现深拷贝的方式有很多,常用的有递归拷贝、JSON.parse()和JSON.stringify()等。

1.递归拷贝

递归拷贝通过递归遍历原对象,将原对象的每个子元素(属性)都逐个复制到新对象上,并且对于原对象的每个子元素如果是引用类型数据,就递归拷贝这个引用类型数据。

//递归拷贝实现函数
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}

//测试递归拷贝函数
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepClone(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

递归拷贝虽然实现简单,但是由于递归遍历可能会导致栈溢出,因此它的性能和可用性并不好。下面介绍另外两种方式。

2.JSON.parse()和JSON.stringify()

将对象转换为JSON字符串,然后再将JSON字符串转换为对象的方式可以实现深拷贝,JSON.parse()和JSON.stringfy()就是这样一种方式。

let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

需要注意的是,通过JSON.stringify()和JSON.parse()方式实现深拷贝,存在一些限制:

  • 这种方式只能复制可枚举属性,而不能复制不可枚举属性和Symbol类型的属性。
  • 这种方式不能复制对象的方法。
  • 如果原对象的某个属性值为undefined、function、symbol,那么新对象中将不包含这些属性。
  • 如果原对象有循环引用的情况(即某个属性引用了对象本身),那么用JSON.stringify()将会抛出异常。

四、拷贝的性能问题

因为深拷贝要递归遍历整个对象,因此深拷贝的效率一般要比浅拷贝慢得多。在代码效率要求很高的情况下,应该优先选择使用浅拷贝而不使用深拷贝。深拷贝可以通过缓存已经处理过的对象来提高效率。

//使用Map缓存已经处理过的对象
function deepCloneWithMap(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  if (map.has(obj)) {
    return map.get(obj);
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  map.set(obj, cloneObj);
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepCloneWithMap(obj[key], map);
    }
  }
  return cloneObj;
}

//测试递归拷贝函数
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepCloneWithMap(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

代码示例

//浅拷贝示例
let obj1 = {a: 1, b: [1,2,3]};
let obj2 = {...obj1};
obj2.b.push(4);
console.log(obj1); //{a: 1, b: [1,2,3,4]}
console.log(obj2); //{a: 1, b: [1,2,3,4]}

//递归拷贝示例
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepClone(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

//JSON.parse和JSON.stringify示例
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}

//使用Map缓存已经处理过的对象的递归拷贝示例
function deepCloneWithMap(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  if (map.has(obj)) {
    return map.get(obj);
  }
  let cloneObj = Array.isArray(obj) ? [] : {};
  map.set(obj, cloneObj);
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[key] = deepCloneWithMap(obj[key], map);
    }
  }
  return cloneObj;
}
let obj1 = {a: 1, b: [1,2,3], c: {d: 4}};
let obj2 = deepCloneWithMap(obj1);
obj2.b.push(4);
obj2.c.d = 5;
console.log(obj1); //{a: 1, b: [1,2,3], c: {d: 4}}
console.log(obj2); //{a: 1, b: [1,2,3,4], c: {d: 5}}