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)) 进行深拷贝时,需要注意以上两个问题,并根据具体情况选择其他深拷贝的方式。

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

相关推荐
Elena_Lucky_baby15 分钟前
实现路由懒加载的方式有哪些?
前端·javascript·vue.js
Domain-zhuo15 分钟前
如何利用webpack来优化前端性能?
前端·webpack·前端框架·node.js·ecmascript
理想不理想v19 分钟前
webpack如何自定义插件?示例
前端·webpack·node.js
小华同学ai37 分钟前
ShowDoc:Star12.3k,福利项目,个人小团队的在线文档“简单、易用、轻量化”还专门针对API文档、技术文档做了优化
前端·程序员·github
一雨方知深秋38 分钟前
智慧商城:封装getters实现动态统计 + 全选反选功能
开发语言·javascript·vue2·foreach·find·every
海威的技术博客40 分钟前
关于JS中的this指向问题
开发语言·javascript·ecmascript
王解1 小时前
Vue CLI 脚手架创建项目流程详解 (2)
前端·javascript·vue.js
刘大浪1 小时前
vue.js滑动到顶便锁定位置
前端·javascript·vue.js
小金刚®1 小时前
构建简洁之美:我的第一个前端页面
前端
ordinary902 小时前
指令-v-for的key
前端·javascript·vue.js