深入理解浅拷贝和深拷贝:JavaScript 中的对象复制

410 阅读5分钟

介绍

在 JavaScript 中,对象的复制是一个常见的操作。而浅拷贝和深拷贝是两种常用的复制方式,每种方式都有其适用的场景和注意事项。本文将深入探讨浅拷贝和深拷贝的概念,并介绍实现它们的不同方法。

浅拷贝的概念和实现方法及原理

浅拷贝方法基于原对象拷贝得到一个新的对象,原对象中内容的修改影响新对象

一个形象的比喻就是自己和自己的影子,主体改变了,影子也会跟着变。

ps: 浅拷贝通常只针对引用类型 原始类型只有深拷贝

Object.create(x)

Object.create(x) 是一种创建新对象的方法,其中 x 是新对象的原型,示例代码如下:

// 原始对象
let person = {
  name: 'John',
};

// 使用 Object.create() 进行浅拷贝
let shallowCopy = Object.create(person);

// 修改原始对象属性
person.name = 'Andy';

//输出浅拷贝对象
console.log(shallowCopy.name);//name为Andy

Object.assign({},a)

这个方法括号中的内容指的是把逗号后面的对象,拼到括号前面的对象里,即把 a 拼到 {} 里,虽然得到了一个新对象,但是里面的内容还是 a

示例代码:

// 源对象
const a = {
  name: '贝贝',
  age: 8,
  address: {
    city: '北京',
  }
};

// 使用 Object.assign() 进行浅拷贝
let c = Object.assign({}, a);

// 输出目标对象
console.log(c);

输出结果为 { name: '贝贝', age: 8, address: { city: '北京' } }

[].concat(x)

.concat() 方法的作用是把括号中的数组合并到前面的数组中,且此方法不会修改原始数组,而是返回一个新的数组的特点,所以我们也可以用这个方法来复制一个数组,即[].concat(x) 示例代码:

let newArr = [].concat(arr)
console.log(newArr)

输出结果为:[ 1, 2, 3, { a: 10 } ]

数组解构

要使用数组解构实现浅拷贝,可以将原始数组解构到一个新的数组变量中。这样做会创建一个新的数组,其中包含与原始数组相同的元素。(先拆开,再合并)

const originalArray = [1, 2, 3, 4, 5];

// 使用数组解构进行浅拷贝
const copiedArray = [...originalArray];//'...'加数组名表示解构该数组,每个元素都独立开

console.log(copiedArray); // 输出 [1, 2, 3, 4, 5]

arr.slice(0)

let arr = [1, 2, 3, 4, 5];

// 使用 slice() 进行浅拷贝
let arr2 = arr.slice(0);//从下标为0开始截出,截到底

console.log(arr2); // 输出 [1, 2, 3, 4, 5]

和.splice()很像的一个方法,功能也很像,这里我们区分一下

arr.splice(1, 1) //括号里的表示:(下标,删几位) ----截掉

arr.slice(1, 3)//左闭右开,此示例指 包1除3 。括号里的表示(下标,截到哪一位/可不写) ---截出来,不影响原数组

arr.toReverser().reverse()

此方法的思路是,先把数组反转并返回一个新数组(.toReverser()),然后再在这个新数组的基础上进行反转(.reverse()

示例代码:

let arr = [1, 2, 3, 4, 5];
let arr2 = arr.toReversed();
console.log(arr2);// 输出: [5, 4, 3, 2, 1] 
let arr3 = arr2.reverse()
console.log(arr3);//输出:[ 1, 2, 3, 4, 5 ]

由此我们也可以了解到 .toReverser().reverse() 方法的区别==>

toReverser()会返回一个新数组,reverse()不会

实现原理

这里我们用代码解释原理:

let obj = {
  name: 'Alo',
  like: {
    a: 'food'
  }
}
function shallow(obj) {
  let newObj = {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {//判断对象元素是否为显示属性
      newObj[key] = obj[key]//是就拷贝
    }

  } //遍历对象  ---隐式具有的属性不拷贝 
  return newObj
}

console.log(shallow(obj));//{xxx}

对于这段代码我们着重看这两行:

  1. for (let key in obj)

这行使用 for...in 循环遍历参数对象 obj 中的每个属性

  1. newObj[key] = obj[key]

点(.)表示法适用于已知属性名的情况,例如 obj.key,其中 key 是属性名。

方括号([])表示法适用于属性名是动态计算的情况,例如 obj[key],其中 key 是一个变量,它的值在运行时确定。通过使用方括号表示法,可以根据变量的值来访问对象的属性。

深拷贝的概念和实现方法及原理

深拷贝方法基于原对象拷贝得到一个新的对象,原对象中内容的修改不会影响新对象

这里可以理解为自己和自己的克隆人,克隆人就算嗝屁了,跟自己也没关系,不会有影响

JSON 序列化和反序列化

我们使用7种原始类型进行测试 示例代码:

let obj = {
  name: 'pingping',
  age: 18,
  like: {
    n: 'coding'
  },
  a: true,
  b: undefined,
  c: null,
  d: Symbol(1),
  e: 123n,
  f: function () {

  }
}
let obj2 = JSON.parse(JSON.stringify(obj))//读不了 bigInt
obj.like.n = 'studying'//没变,是coding

console.log(obj2);

运行结果是这样的:

屏幕截图 2024-05-22 225828.png 它说它不能识别BigInt类型,好,那我们删掉e: 123n,这行再运行

image.png 出了鬼,没有b,d,f。

好,现在我们可以进行总结了,虽然此方法可以复制,但有以下几个缺点:

缺点:

  • 不能识别BigInt类型
  • 不能拷贝 undefined Symbol function 类型的值
  • 不能处理循环引用

structuredClone()

此方法代码非常简洁:

const obj = { name: "Alo", age: 19};
const clonedObj = structuredClone(obj);
console.log(clonedObj);  // { name: "Alo", age: 19}

相较之下真的很方便,BUT!structuredClone() 方法是一个新功能,并不是所有浏览器都支持。在某些浏览器中,可能需要使用其他方法或库来实现深拷贝操作

实现原理

原理采用了递归的思想:

const user = {
  name: {
    firstName: 'Alo',
    lastName: '365'
  },
  age: 19
}

function deep(obj) {
  let newObj = {}


  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {//判断是否为显示属性
      if (obj[key] instanceof Object) {//typeof (obj[key]) === 'object'&& obj[key] !== null
        newObj[key] = deep(obj[key])//递归  --自己调用自己
      } else {
        newObj[key] = obj[key]
      }

    }
  }
  return newObj
}

console.log(deep(user));
  1. 借助for in遍历 原对象,将原对象的属性值增加在新对象中
  2. 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断拷贝的属性是不是对象显示具有的
  3. 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型,递归创建新的子对象

总结

浅拷贝和深拷贝是 JavaScript 中对象复制的两种常见方式。浅拷贝只复制对象或数组的第一层属性或元素,而深拷贝会递归复制所有嵌套属性或元素,创建完全独立的副本。本文介绍了实现浅拷贝和深拷贝的常见方法,并比较了它们的优缺点。

希望本文内容能对你提供帮助,下篇文章再见~