深拷贝和浅拷贝的实现方法和区别

深拷贝和浅拷贝的实现方法和区别

前言

了解深浅拷贝需要先了解两种数据类型,基本类型和引用类型。

基本类型

基本类型是简单的数据类型,它们存储的是值本身。在内存中,基本类型的值直接存储在变量的位置。JavaScript中的基本类型有:

  1. Number(数字):整数或浮点数。

    javascript 复制代码
    let num = 42; // 数字
    let pi = 3.14; // 浮点数
  2. String(字符串):字符序列。

    javascript 复制代码
    let str = "Hello, World!"; // 字符串
  3. Boolean(布尔值):表示真或假。

    javascript 复制代码
    let isTrue = true; // 真
    let isFalse = false; // 假
  4. Undefined(未定义):表示未初始化的变量。

    javascript 复制代码
    let undefinedVar;
  5. Null(空值):表示没有值或空对象引用。

    javascript 复制代码
    let nullVar = null;
  6. Symbol(符号):ES6引入的一种唯一标识符。

    javascript 复制代码
    let sym = Symbol('unique');

引用类型

引用类型是由多个值构成的对象,它们存储的是对象的引用(内存地址)。当操作引用类型时,实际上是在操作它们的引用而不是直接操作值。JavaScript中的引用类型包括:

  1. Object(对象):包含键值对的集合。

    javascript 复制代码
    let person = {
      name: 'John',
      age: 30,
    };
  2. Array(数组):包含有序元素列表的对象。

    javascript 复制代码
    let numbers = [1, 2, 3, 4, 5];
  3. Function(函数):可执行的代码块。

    javascript 复制代码
    function greet(name) {
      console.log(`Hello, ${name}!`);
    }
  4. Date(日期):表示日期和时间的对象。

    javascript 复制代码
    let currentDate = new Date();
  5. RegExp(正则表达式):用于匹配字符串的模式。

    javascript 复制代码
    let regex = /[a-z]/;

引用类型的值在内存中是通过引用存储的,因此对于相同的引用类型,它们可以共享相同的引用,即使它们在逻辑上是不同的对象。这就是为什么在进行浅拷贝时,只有引用被复制,而不是引用指向的对象的实际内容。深拷贝则是一种创建引用类型完全独立副本的方法。

1.浅拷贝

1.基本说明

浅拷贝是指创建一个新的对象或数组,复制源对象或数组的第一层元素到新对象或数组中。浅拷贝会复制基本类型的值直接到新对象,但对于引用类型(例如对象或数组),它只会复制它们的引用,而不会递归地复制它们的内部元素。简而言之,浅拷贝创建了一个新的对象或数组,但只复制了原始数据结构的表面层次,不会递归复制嵌套在原始结构中的对象或数组。

浅拷贝的特点是在创建副本时只复制原始对象或数组的第一层元素,而不会递归复制嵌套在其中的对象或数组。因此,如果你对浅拷贝的副本进行修改,这些修改可能会影响到原始对象或数组的第一层元素。但如果修改的是副本内的嵌套对象或数组,原始对象或数组不会受到影响。

javascript 复制代码
// 原始对象
const ABC = {
    key1: 'value1',
    key2: 'value2',
    abc: {
      key3: 'value3',
      key4: 'value4'
    }
  };
  
  // 使用浅拷贝创建副本
  const ABCcopy = { ...ABC };
  
  // 修改浅拷贝的第一层元素
  ABCcopy.key1 = '第一层元素';
  
  // 修改浅拷贝的嵌套对象
  ABCcopy.abc.key3 = '嵌套对象';
  
  console.log(ABC);
//   {
//     key1: 'value1',
//     key2: 'value2',
//     abc: { key3: '嵌套对象', key4: 'value4' }
//   }
  console.log(ABCcopy);
//   {
//     key1: '第一层元素',
//     key2: 'value2',
//     abc: { key3: '嵌套对象', key4: 'value4' }
//   }

ABCcopy 是通过扩展运算符进行浅拷贝的。修改 ABCcopy 的第一层元素(key1)不会影响到原始对象,因为它们是基本类型值。然而,修改 ABCcopy 的嵌套对象(abc)的属性(key3)将会影响到原始对象的相应属性,因为它们是引用类型,浅拷贝只复制了引用。

所以,浅拷贝改变后,如果修改的是第一层元素,原对象或数组不受影响;但如果修改的是嵌套在其中的引用类型,原对象或数组可能会受到影响。这是因为浅拷贝只复制了引用,而不是引用指向的实际内容。

2.浅拷贝实现方法

普遍:

1.手动遍历复制对象属性

javascript 复制代码
function CopyObject(obj) {
  const copy = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = obj[key];
    }
  }
  return copy;
}

const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = CopyObject(ABC);

对象:

2. 扩展运算符(...)

javascript 复制代码
const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = { ...ABC };

3. Object.assign()

javascript 复制代码
const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = Object.assign({}, ABC);

4. 使用 Object.create()

javascript 复制代码
const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = Object.create(ABC);

数组:

5. Array.slice()

javascript 复制代码
const ABC = [1, 2, 3, 4, 5];
const ABCCopy = ABC.slice(1,2);

console.log(ABC)  //[ 1, 2, 3, 4, 5 ]
console.log(ABCCopy) //[ 2 ]

6. Array.concat()

javascript 复制代码
const ABC = [1, 2, 3, 4, 5];
const ABCCopy = [999,888,777].concat(ABC);

console.log(ABC)  //[1, 2, 3, 4, 5]
console.log(ABCCopy) //[999,888,777,1,2,3,4,5]

7. 使用 Array.from() 复制数组

Array.from() 是 JavaScript 中的一个静态方法,用于从一个类数组对象或可迭代对象创建一个新的数组实例。该方法接受两个参数:第一个参数是要转换成数组的对象,第二个参数是一个可选的映射函数,用于对数组的每个元素进行转换。

基本语法如下:

javascript 复制代码
Array.from(arrayLike [, mapFunction [, thisArg]])
  • arrayLike: 要转换成数组的对象或可迭代对象。
  • mapFunction(可选): 对数组中的每个元素执行的映射函数。
  • thisArg(可选): 映射函数中 this 的值。
javascript 复制代码
const ABC = [1, 2, 3, 4, 5];
const ABCCopy = Array.from(ABC);

字符串:

8. 使用 slice() 复制字符串

javascript 复制代码
const ABC = "Hello, World!";
const ABCCopy = ABC.slice();

这些方法都可以用于创建原始对象或数组的浅拷贝,但需要注意的是,对于嵌套结构,这些方法只会复制嵌套对象或数组的引用,而不会创建它们的深层副本。如果需要深拷贝嵌套结构,需要考虑其他方法,例如手动递归遍历对象的属性。

2.深拷贝

1.基本说明

深拷贝是指在复制对象或数组时,不仅复制了原始对象或数组的第一层元素,还递归地复制了其内部所有层次的嵌套对象或数组,从而创建一个完全独立的副本。深拷贝确保了副本和原始对象之间的所有层次都是相互独立的,互不影响。

深拷贝通常通过递归遍历对象或数组的所有层次来实现,确保每个嵌套的对象或数组都被完全复制

对于深拷贝而言,副本将独立于原始对象,并且对副本的修改不会影响原始对象。深拷贝会递归复制对象的所有层,包括嵌套的对象或数组,以确保副本是完全独立的。

js 复制代码
npm i lodash //安装依赖库
javascript 复制代码
const _ = require('lodash')
const ABC = {
    key1: 'value1',
    key2: 'value2',
    abc: {
      key3: 'value3',
      key4: 'value4'
    }
  };
  
  // 使用深拷贝库,如Lodash中的_.cloneDeep()
  const deepCopy = _.cloneDeep(ABC);
  
  // 修改深拷贝的第一层元素
  deepCopy.key1 = '第一层元素';
  
  // 修改深拷贝的嵌套对象
  deepCopy.abc.key3 = '嵌套对象';
  
  console.log(ABC);
//   {
//     key1: 'value1',
//     key2: 'value2',
//     abc: { key3: 'value3', key4: 'value4' }
//   }
  console.log(deepCopy);
//   {
//     key1: '第一层元素',
//     key2: 'value2',
//     abc: { key3: '嵌套对象', key4: 'value4' }
//   }

使用 Lodash 库的 _.cloneDeep() 方法来执行深拷贝。无论修改的是第一层元素还是嵌套的对象,都不会影响到原始对象。这是因为深拷贝递归地创建了每个对象的副本,确保了所有嵌套结构的独立性。

深拷贝的特点是创建一个原始对象的完全独立副本,不受原始对象或副本之间修改的相互影响。这在需要保持数据完整性和避免副作用的情况下非常有用。

2.深拷贝实现方法

深拷贝的实现方法有很多,以下是一些常见的深拷贝方式:

1. 递归手动实现

通过递归遍历对象或数组的所有层次,创建相应的副本。

javascript 复制代码
function deepCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let result = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepCopy(obj[key]);
    }
  }

  return result;
}

const ABC = { key1: 'value1', key2: { abc: 'abcValue' } };
const deepCopyObj = deepCopy(ABC);

2. JSON 序列化与反序列化(JSON.parse和JSON.stringify)

JSON.parse() 用于解析 JSON 字符串,将其转换为相应的 JavaScript 对象。

JSON.stringify() 用于将 JavaScript 对象转换为 JSON 字符串。

通过将对象转换为JSON字符串,然后再将其解析回对象,实现深拷贝。这种方法有一些限制,例如无法处理包含函数、RegExp等特殊对象的情况。

javascript 复制代码
const ABC = { key1: 'value1', key2: { abc: 'abcValue' } };
const deepCopyObj = JSON.parse(JSON.stringify(ABC));

3. 使用第三方库Lodash

许多第三方库提供了深拷贝的方法,其中最常用的是 Lodash 库的 _.cloneDeep() 方法。

javascript 复制代码
const _ = require('lodash');
const ABC = { key1: 'value1', key2: { abc: 'abcValue' } };
const deepCopyObj = _.cloneDeep(ABC);

4. MessageChannel

在浏览器环境下,可以使用 MessageChannel 来创建对象的副本。

MessageChannel 是 HTML Living Standard 中定义的一种用于在不同上下文之间进行通信的 API。它主要用于在 Web 开发中实现跨文档、跨窗口、跨 iframe、跨文档对象模型 (DOM) 或者主线程和 Web Worker 之间进行异步消息传递。MessageChannel 创建了一个双向通信通道,通过两个相关联的 MessagePort 实例进行通信。

MessageChannel 的特点

  1. 双向通信: MessageChannel 提供了两个 MessagePort 对象,分别命名为 port1port2,它们都可以用于发送和接收消息。
  2. 消息传递: 通过调用 postMessage() 方法,可以在一个端口上发送消息,而通过在另一个端口上监听 message 事件,可以接收消息。
  3. 传递通道: MessageChannel 通常用于传递一次性或大块的数据,例如,可以使用 Transferable 对象(例如,ArrayBuffer)来传递大型数据结构,而无需复制数据。
  4. 跨上下文通信: 可以在主线程和 Web Worker、不同的窗口或 iframe 之间使用 MessageChannel 进行通信。
javascript 复制代码
function deepCopyMessage(obj) {
  return new Promise(resolve => {
    const channel = new MessageChannel();
    channel.port1.onmessage = event => resolve(event.data);
    channel.port2.postMessage(obj);
  });
}

const ABC = { key1: 'value1', key2: { abc: 'abcValue' } };

deepCopyMessage(ABC).then(deepCopyObj => {
  console.log(deepCopyObj);
});

需要注意的是,并非所有对象都能被上述方法完美地深拷贝,例如包含循环引用、函数、RegExp等特殊对象的情况。在实际使用中,需要根据具体的需求选择最适合的深拷贝方式。

3.深浅拷贝主要区别

  1. 对象结构复制:
    • 浅拷贝: 只复制对象的第一层属性,如果对象的属性值是对象,那么拷贝后的对象会引用相同的对象。
    • 深拷贝: 复制整个对象结构,包括对象的所有嵌套属性,递归复制每个子对象,确保拷贝后的对象和原始对象是完全独立的。
  2. 引用关系:
    • 浅拷贝: 对象的引用关系仅在第一层生效,即拷贝后的对象和原始对象的第一层属性是独立的,但如果属性值是对象,则两者之间共享相同的子对象。
    • 深拷贝: 对象的引用关系在所有层级都被打破,确保拷贝后的对象和原始对象及其所有嵌套对象都是独立的,互不影响。
  3. 循环引用处理:
    • 浅拷贝: 由于只复制第一层属性,对于包含循环引用的对象,浅拷贝可能陷入无限循环,导致栈溢出。
    • 深拷贝: 通常需要额外的处理来解决循环引用问题,因为简单的递归复制可能导致无限递归。一些深拷贝实现会使用一些策略,例如记录已经拷贝过的对象,以避免重复拷贝。
  4. 性能:
    • 浅拷贝: 通常比深拷贝更高效,因为它只复制对象的第一层属性,不需要递归整个对象结构。
    • 深拷贝: 由于需要递归复制整个对象结构,深拷贝可能会消耗更多的时间和内存,尤其是在处理大型对象或对象树时。
相关推荐
阿珊和她的猫3 小时前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
PAK向日葵5 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
加班是不可能的,除非双倍日工资7 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip8 小时前
vite和webpack打包结构控制
前端·javascript
excel8 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼8 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy9 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT9 小时前
promise & async await总结
前端