JavaScript 对象拷贝方法详解:浅拷贝与深拷贝

前言

在 JavaScript 开发中,经常会遇到对象拷贝的需求,尤其是在处理复杂的数据结构或者状态管理时。对象拷贝分为浅拷贝和深拷贝两种方式,它们在处理对象引用关系上有着不同的表现。本文将详细介绍 JavaScript 中实现对象拷贝的方法,包括浅拷贝和深拷贝的实现原理和常见方式,并探讨它们的优缺点以及适用场景,帮助初学者更好地理解和应用对象拷贝的相关概念,提升对 JavaScript 对象拷贝的理解和运用能力。

浅拷贝

浅拷贝是指复制一个对象,但是原对象和拷贝对象仍然共享同一个内存地址。这意味着如果原对象发生变化,拷贝对象也会相应地发生变化。

需要注意的是,对于基本类型(如数字、字符串等),进行浅拷贝时会直接复制其值,而不是引用。

常见的浅拷贝方法

1. Object.create(obj)

使用 Object.create方法可以创建一个新对象,新对象的原型链指向被拷贝的对象。但是这种方式只会拷贝对象的属性,而不会拷贝对象的方法。

当使用 Object.create(obj)创建一个新对象时,新对象的原型链会指向obj 对象。这意味着可以通过原型链在新对象上访问到 obj对象的属性和方法。

js 复制代码
 let a = {
    name: 'tom'
 }
 let b = Object.create(a)
 a.name = 'black'
 console.log(b.name); // 打印 black

Object.create(a) 创建了一个新对象 b,它继承了a对象的原型链,因此能够访问a,对象中定义的name属性。

需要注意的是,使用 Object.create(obj)方法创建的新对象不会直接复制 obj对象的所有属性和方法,而是通过原型链进行访问。如果你需要实现对象的拷贝,可以使用其他方法,如 Object.assign({},obj)

2. Object.assign({}, obj)

使用 Object.assign方法可以将源对象的所有可枚举属性复制到目标对象中。该方法会返回目标对象。需要注意的是,该方法只会拷贝对象的第一层属性,如果对象中有嵌套对象,则仍然是浅拷贝。

js 复制代码
const obj = {
  name: 'John',
  age: 30,
  hobbies: ['reading', 'cooking'],
  address: {
    city: 'New York',
    country: 'USA'
  }
};
const newObj = Object.assign({}, obj);  // 现在使用 `Object.assign()` 方法进行浅拷贝
obj.hobbies.push('painting');
obj.address.city = 'San Francisco';

此时,newObj 对象将变为:

js 复制代码
{
  name: 'John',
  age: 30,
  hobbies: [ 'reading', 'cooking', 'painting' ],
  address: { city: 'San Francisco', country: 'USA' }
}

使用 Object.assign({}, obj) 方法来实现对象的浅拷贝,newObj 对象虽然是通过 Object.assign() 方法从 obj 对象浅拷贝而来,但对于嵌套对象和数组这样的引用类型的属性,仍然只是复制了它们的引用而不是实际值。

因此,当对原始对象中的嵌套对象或数组进行修改时,新对象中对应的属性也会受到影响,这就是浅拷贝的特性所导致的。

在对 obj.hobbies 数组和 obj.address.city 进行了修改时,也导致了 newObj 中相应属性的数值也发生了变化

3. [].concat(arr)

对于数组的浅拷贝,我们可以使用concat 方法。它会将原数组中的所有元素复制到一个新数组中。

js 复制代码
let arr=[1,2,3,{n:10}]
let newArr=[].concat(arr)
arr[3].n=100
console.log(newArr);  //[ 1, 2, 3, { n: 100 } 

使用 concat 方法来创建新数组,可以实现数组的浅拷贝。因为 concat 方法会将原始数组中的元素复制到新数组中,而针对对象等引用类型的元素,仍然只是复制其引用而不是实际值。因此,修改原始数组中的嵌套对象时,新数组中对应的元素也会受到影响

4. 数组解构

对于数组,我们可以使用解构语法来进行浅拷贝。例如,[...arr] 可以创建一个新数组,其中包含原数组的所有元素。

js 复制代码
let newArr=[...arr]
arr[3].n=100
 console.log(newArr);//[ 1, 2, 3, { n: 100 } ]

使用解构语法 [...arr] 进行浅拷贝时,只能复制数组的第一层元素,而无法复制嵌套对象的引用。所以通过 arr[3].n = 100 修改了原始数组 arr 的第四个元素时,新数组 newArr 的第四个元素也发生了改变。

这是因为在浅拷贝中,数组的元素仅进行了浅层复制,对于对象或数组等引用类型的元素,只复制了它们的引用而不是实际的值。因此,当修改原始数组中的嵌套对象时,新数组中对应的元素也会受到影响。

深拷贝

深拷贝是指创建一个全新的对象,并且递归地复制所有嵌套对象的值,这样原对象和拷贝对象完全独立,互不影响。在 JavaScript 中,常用的深拷贝方法是使用JSON.parse(JSON.stringify(obj))

JSON.parse(JSON.stringify(obj))

JSON.parse(JSON.stringify(obj))是一种常用的将对象进行深拷贝的方法。它通过先将对象转换为 JSON 字符串,然后再将字符串解析为新的对象来实现深拷贝。

下面是一个示例:

js 复制代码
const obj = {
  name: 'John',
  age: 30,
  address: {
    city: 'New York',
    country: 'USA'
  }
};

const newObj = JSON.parse(JSON.stringify(obj));

obj.name = 'Jane';
obj.address.city = 'San Francisco';

console.log(newObj.name); // 输出 "John"
console.log(newObj.address.city); // 输出 "New York"

在上面的示例中,我们首先定义了一个对象obj ,其中包含了属性nameageaddress。然后,我们使用 JSON.stringify(obj)obj 对象转换为 JSON 字符串,并使用 JSON.parse()将该字符串解析为一个新的对象 newObj。这样,newObj 就成为了obj 的深拷贝。

接着,我们修改了 obj对象的一些属性值,包括 nameaddress.city。但是由于newObjobj 的深拷贝,它们之间是完全独立的,因此对obj 的修改不会影响到newObj

但是该方法也存在缺陷

JSON.parse(JSON.stringify(obj))的缺陷

虽然 JSON.parse(JSON.stringify(obj))可以实现深拷贝,但是它有一些缺陷需要注意:

  1. 无法处理 undefined、function、Symbol 等特殊类型,这些类型在转换过程中会丢失。
  2. 无法处理循环引用,即对象中存在循环引用关系时会导致堆栈溢出或报错。

因此,在使用JSON.parse(JSON.stringify(obj)) 进行深拷贝时,需要注意以上两个问题,并根据具体情况选择其他深拷贝的方式。

总结起来,浅拷贝适用于简单的对象或数组的拷贝,而对于复杂的对象或需要完全隔离的拷贝需求,深拷贝是更好的选择。在实际开发中,根据具体的需求和场景选择合适的拷贝方式是非常重要的。

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试