深浅拷贝:数据复制的哲学与实践

108 阅读5分钟

JavaScript中深浅拷贝的深入解析及应用场景

在JavaScript开发中,理解深浅拷贝的概念是至关重要的。

浅拷贝和深拷贝决定了数据的复制方式,对程序的运行效果和性能都有着深远的影响。

本文将详细探讨两者的定义、实现方法以及各自的优缺点和适用场景。

一、浅拷贝与深拷贝的定义

1. 浅拷贝

浅拷贝是对对象或数组的一层属性进行拷贝,对于基本类型(如数值、字符串、布尔值)会进行值拷贝,但对于引用类型(如对象、数组),则只会复制引用地址。

也就是说,浅拷贝后的对象与原对象共享内部引用类型的数据。

2. 深拷贝

深拷贝则是递归地拷贝所有层级的数据,不仅包括第一层,还包括所有嵌套的对象或数组。

因此,深拷贝后的对象与原对象完全独立,互不影响。

二、浅拷贝的实现方法及特点

1. 浅拷贝的常用方法

  • 扩展运算符(Spread Operator)

let a = { en: 'Hello', de: 'Hallo', es: 'Hola', pt: 'Olá'};

let b = { ...a };

b.pt = 'Oi'; // 修改b中的pt属性

console.log(a.pt); // Olá

console.log(b.pt); // Oi
  • Object.assign()

let target = {};

let source = { a: 1, b: 2 };

let returnedTarget = Object.assign(target, source);

returnedTarget.b = 3;

console.log(source.b); // 输出2
  • Array.prototype.slice()

let arr = [1, 2, 3];

let newArr = arr.slice();

newArr[0] = 'one';

console.log(arr); // ["one", 2, 3]

console.log(newArr); // ["one", 2, 3]
  • 手动遍历对象属性

function shallowCopy(obj) {

 let copy = {};

 for (let key in obj) {

 if (obj.hasOwnProperty(key)) {

 copy[key] = obj[key];

 }

 }

 return copy;

}

2. 浅拷贝的特点

  • 优点
  • 实现简单,性能高,占用内存少。
  • 适合浅层次对象的快速复制。
  • 缺点
  • 只拷贝对象的第一层,深层次嵌套的对象依然共享引用。
  • 修改拷贝对象中的深层次内容时会影响到原对象。

3. 浅拷贝的实际应用案例

浅拷贝常用于需要快速创建类似对象的场景,比如在某些属性需要修改而大部分属性保持不变的情况下。

例如:


let original = { name: 'Alice', age: 25, address: { city: 'Wonderland', street: 'Queen's Drive' } };

let shallowCopied = Object.assign({}, original);

shallowCopied.address.city = 'New Wonderland';

console.log(original.address.city); // New Wonderland

在这个例子中,浅拷贝适用于只需要改变部分属性而保持其他属性不变的情况。

三、深拷贝的实现方法及特点

1. 深拷贝的常用方法

  • JSON.stringify 与 JSON.parse

let a = {a:1, b:2};

let b = JSON.parse(JSON.stringify(a));

a.a = 11;

console.log(a); // {a:11, b:2}

console.log(b); // {a:1, b:2}
  • 递归方法

function deepClone(obj, hash = new WeakMap()) {

 if (obj === null || typeof obj !

== 'object') return obj;

 if (hash.has(obj)) return hash.get(obj);

 let result = Array.isArray(obj) ? [] : {};

 hash.set(obj, result);

 for (let key in obj) {

 if (obj.hasOwnProperty(key)) {

 result[key] = deepClone(obj[key], hash);

 }

 }

 return result;

}
  • 第三方库 lodash 的 _.cloneDeep

const _ = require('lodash');

let a = {a:1, b:{ c:2 }};

let b = _.cloneDeep(a);

b.b.c = 3;

console.log(a); // {a:1, b:{ c:2 }}

console.log(b); // {a:1, b:{ c:3 }}
  • 手动遍历并递归

function deepCopy(obj) {

 if (obj === null || typeof obj !

== 'object') return obj;

 let copy = Array.isArray(obj) ? [] : {};

 for (let key in obj) {

 if (obj.hasOwnProperty(key)) {

 copy[key] = deepCopy(obj[key]);

 }

 }

 return copy;

}

2. 深拷贝的特点

  • 优点
  • 完整复制对象的所有层级,修改新对象不会影响原对象。
  • 确保数据的完整性和独立性。
  • 缺点
  • 实现复杂,性能较差,占用更多内存。
  • 对于含有函数、循环引用等复杂对象可能会遇到问题。

3. 深拷贝的实际应用案例

深拷贝在需要确保数据独立性的场景下非常重要。

例如:

  • 避免数据污染:在多线程编程或者异步操作中,深拷贝可以防止意外修改共享数据。
  • 状态管理:在使用Redux等状态管理库时,深拷贝可以确保状态树的不可变性。

例如:


const state = { count: 1, details: { user: 'Alice', loggedIn: true } };

const newState = JSON.parse(JSON.stringify(state));

newState.details.loggedIn = false;

console.log(state.details.loggedIn); // true,原始状态未受影响
  • 持久化存储:深拷贝可以将整个对象序列化后保存到本地存储或传输到服务器。

例如: } };

const storedData = JSON.stringify(userData);

localStorage.setItem('userData', storedData);


### 四、深浅拷贝的选择与最佳实践

#### 1. 根据需求选择合适的拷贝方式

- 如果只是简单对象的浅层级复制,且不涉及深层次嵌套的数据,浅拷贝更为高效。

例如,配置项的复制。

- 如果需要复制复杂对象,并且要求修改拷贝后的对象不影响原对象,应选择深拷贝。

例如,状态管理和持久化存储等场景。

#### 2. 注意性能问题

- 浅拷贝的性能通常优于深拷贝,因为它只复制一层数据。

在处理大量数据时,浅拷贝更为高效。

- 深拷贝由于递归复制所有层级的数据,性能较差,内存消耗也更大。

在性能敏感的场景中,应谨慎使用深拷贝。

- JSON序列化的方法不适用于包含函数、`undefined`、循环引用等特殊情况的对象。

这种情况下可以使用递归方法或第三方库来实现深拷贝。

#### 3. 使用工具和库提高效率和可靠性

- 现代JavaScript框架和库(如React、Vue等)通常会提供便捷的深拷贝方法或插件。

例如,React的immutable更新机制通过浅拷贝实现高效的状态管理。

- Lodash库提供了丰富的工具函数,如`_.cloneDeep`,可以方便地进行深拷贝操作。

同时,它也能处理复杂的数据结构如循环引用等。

此外,immer.js这样的不可变数据结构库,可以在保证数据不变的同时提高开发效率和代码可维护性。

### 五、总结

深浅拷贝作为JavaScript中的重要概念,理解并合理应用它们能够极大地提高代码的稳定性和维护性。

通过本文的介绍,相信读者已经对深浅拷贝有了全面的认识,并能在实际项目中灵活运用。

无论是优化性能还是确保数据一致性,深浅拷贝都提供了有效的解决方案。