返回

面向对象的JavaScript,你需要知道的深拷贝

前端

为什么要学习深拷贝?

作为一名经验丰富的JavaScript开发人员,我经常在项目中遇到对象拷贝问题。JavaScript中存在两种拷贝方式:浅拷贝和深拷贝,区别在于对引用数据类型的处理。

基本数据类型(包括undefined、number、boolean、bigint和string)在内存中直接存储值,因此浅拷贝和深拷贝没有区别。

而引用数据类型(包括object、array和function)在内存中存储的是指向实际值的引用,浅拷贝只拷贝这个引用,而深拷贝则会拷贝引用所指向的实际值。这意味着深拷贝可以创建一个新对象,其中包含原始对象的完全副本,而浅拷贝创建的新对象仍然引用原始对象的同一个内存地址。

初学者往往会混淆浅拷贝和深拷贝,导致难以捉摸的程序错误。通过学习深拷贝,您可以避免这些陷阱,编写更健壮可靠的JavaScript代码。

基本数据类型和引用数据类型

在进一步探讨深拷贝之前,让我们先来回顾一下基本数据类型和引用数据类型的区别。

数据类型 存储方式 浅拷贝/深拷贝
基本数据类型(undefined、number、boolean、bigint、string) 直接存储值 无区别
引用数据类型(object、array、function) 存储指向实际值的引用 浅拷贝只拷贝引用,深拷贝拷贝引用所指向的实际值

为了更好地理解,我们来看一个例子:

const num1 = 10;
const num2 = num1;

console.log(num1); // 输出:10
console.log(num2); // 输出:10

num1 = 20;

console.log(num1); // 输出:20
console.log(num2); // 输出:10

在这个例子中,我们定义了两个变量num1和num2,并让num2等于num1。此时,num1和num2都指向同一个内存地址,即存储数字10的地址。当我们把num1的值改为20时,num1指向了另一个内存地址,即存储数字20的地址,而num2仍然指向原来的内存地址,即存储数字10的地址。

这就是基本数据类型的浅拷贝行为。当我们拷贝基本数据类型的值时,实际上是复制了这个值本身,而不是复制指向该值的引用。

深拷贝

让我们回到深拷贝。深拷贝可以创建一个新对象,其中包含原始对象的完全副本。这意味着新对象和原始对象完全独立,彼此之间的任何更改都不会相互影响。

在JavaScript中,我们可以使用两种方法来实现深拷贝:

  1. JSON.parse(JSON.stringify()):这种方法将对象转换成JSON字符串,然后再将JSON字符串解析成一个新的对象。
  2. 递归:这种方法使用递归来遍历对象,并为每个属性创建一个新值。

以下是一个使用JSON.parse(JSON.stringify())方法实现深拷贝的例子:

const originalObject = {
  name: 'John Doe',
  age: 30,
  address: {
    street: '123 Main Street',
    city: 'New York',
    state: 'NY'
  }
};

const copyObject = JSON.parse(JSON.stringify(originalObject));

console.log(originalObject); // 输出:{ name: 'John Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }
console.log(copyObject); // 输出:{ name: 'John Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }

originalObject.name = 'Jane Doe';

console.log(originalObject); // 输出:{ name: 'Jane Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }
console.log(copyObject); // 输出:{ name: 'John Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }

在这个例子中,我们使用JSON.parse(JSON.stringify())方法对originalObject进行深拷贝,创建了一个新的对象copyObject。然后,我们修改了originalObject的name属性,并将值改为'Jane Doe'。此时,我们发现copyObject的name属性仍然是'John Doe',这说明深拷贝成功地创建了一个与originalObject完全独立的新对象。

以下是一个使用递归方法实现深拷贝的例子:

function deepCopy(object) {
  if (typeof object !== 'object' || object === null) {
    return object;
  }

  const copy = Array.isArray(object) ? [] : {};

  for (const key in object) {
    copy[key] = deepCopy(object[key]);
  }

  return copy;
}

const originalObject = {
  name: 'John Doe',
  age: 30,
  address: {
    street: '123 Main Street',
    city: 'New York',
    state: 'NY'
  }
};

const copyObject = deepCopy(originalObject);

console.log(originalObject); // 输出:{ name: 'John Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }
console.log(copyObject); // 输出:{ name: 'John Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }

originalObject.name = 'Jane Doe';

console.log(originalObject); // 输出:{ name: 'Jane Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }
console.log(copyObject); // 输出:{ name: 'John Doe', age: 30, address: { street: '123 Main Street', city: 'New York', state: 'NY' } }

在这个例子中,我们使用递归方法对originalObject进行深拷贝,创建了一个新的对象copyObject。然后,我们修改了originalObject的name属性,并将值改为'Jane Doe'。此时,我们发现copyObject的name属性仍然是'John Doe',这说明深拷贝成功地创建了一个与originalObject完全独立的新对象。

结语

深拷贝是JavaScript开发中一项重要的技术,可以帮助我们避免浅拷贝带来的问题。通过学习深拷贝,我们可以编写更健壮可靠的JavaScript代码。