了解深浅拷贝需要先了解两种数据类型,基本类型和引用类型。
基本类型是简单的数据类型,它们存储的是值本身。在内存中,基本类型的值直接存储在变量的位置。JavaScript中的基本类型有:
Number(数字):整数或浮点数。
let num = 42; // 数字
let pi = 3.14; // 浮点数
String(字符串):字符序列。
let str = "Hello, World!"; // 字符串
Boolean(布尔值):表示真或假。
let isTrue = true; // 真
let isFalse = false; // 假
Undefined(未定义):表示未初始化的变量。
let undefinedVar;
Null(空值):表示没有值或空对象引用。
let nullVar = null;
Symbol(符号):ES6引入的一种唯一标识符。
let sym = Symbol('unique');
引用类型是由多个值构成的对象,它们存储的是对象的引用(内存地址)。当操作引用类型时,实际上是在操作它们的引用而不是直接操作值。JavaScript中的引用类型包括:
Object(对象):包含键值对的集合。
let person = {
name: 'John',
age: 30,
};
Array(数组):包含有序元素列表的对象。
let numbers = [1, 2, 3, 4, 5];
Function(函数):可执行的代码块。
function greet(name) {
console.log(`Hello, ${name}!`);
}
Date(日期):表示日期和时间的对象。
let currentDate = new Date();
RegExp(正则表达式):用于匹配字符串的模式。
let regex = /[a-z]/;
引用类型的值在内存中是通过引用存储的,因此对于相同的引用类型,它们可以共享相同的引用,即使它们在逻辑上是不同的对象。这就是为什么在进行浅拷贝时,只有引用被复制,而不是引用指向的对象的实际内容。深拷贝则是一种创建引用类型完全独立副本的方法。
浅拷贝是指创建一个新的对象或数组,复制源对象或数组的第一层元素到新对象或数组中。浅拷贝会复制基本类型的值直接到新对象,但对于引用类型(例如对象或数组),它只会复制它们的引用,而不会递归地复制它们的内部元素。简而言之,浅拷贝创建了一个新的对象或数组,但只复制了原始数据结构的表面层次,不会递归复制嵌套在原始结构中的对象或数组。
浅拷贝的特点是在创建副本时只复制原始对象或数组的第一层元素,而不会递归复制嵌套在其中的对象或数组。因此,如果你对浅拷贝的副本进行修改,这些修改可能会影响到原始对象或数组的第一层元素。但如果修改的是副本内的嵌套对象或数组,原始对象或数组不会受到影响。
// 原始对象
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
)将会影响到原始对象的相应属性,因为它们是引用类型,浅拷贝只复制了引用。
所以,浅拷贝改变后,如果修改的是第一层元素,原对象或数组不受影响;但如果修改的是嵌套在其中的引用类型,原对象或数组可能会受到影响。这是因为浅拷贝只复制了引用,而不是引用指向的实际内容。
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);
const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = { ...ABC };
const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = Object.assign({}, ABC);
const ABC = { key1: 'value1', key2: 'value2' };
const ABCCopy = Object.create(ABC);
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 ]
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]
Array.from()
是 JavaScript 中的一个静态方法,用于从一个类数组对象或可迭代对象创建一个新的数组实例。该方法接受两个参数:第一个参数是要转换成数组的对象,第二个参数是一个可选的映射函数,用于对数组的每个元素进行转换。
基本语法如下:
Array.from(arrayLike [, mapFunction [, thisArg]])
arrayLike
: 要转换成数组的对象或可迭代对象。mapFunction
(可选): 对数组中的每个元素执行的映射函数。thisArg
(可选): 映射函数中 this
的值。const ABC = [1, 2, 3, 4, 5];
const ABCCopy = Array.from(ABC);
const ABC = "Hello, World!";
const ABCCopy = ABC.slice();
这些方法都可以用于创建原始对象或数组的浅拷贝,但需要注意的是,对于嵌套结构,这些方法只会复制嵌套对象或数组的引用,而不会创建它们的深层副本。如果需要深拷贝嵌套结构,需要考虑其他方法,例如手动递归遍历对象的属性。
深拷贝是指在复制对象或数组时,不仅复制了原始对象或数组的第一层元素,还递归地复制了其内部所有层次的嵌套对象或数组,从而创建一个完全独立的副本。深拷贝确保了副本和原始对象之间的所有层次都是相互独立的,互不影响。
深拷贝通常通过递归遍历对象或数组的所有层次来实现,确保每个嵌套的对象或数组都被完全复制
对于深拷贝而言,副本将独立于原始对象,并且对副本的修改不会影响原始对象。深拷贝会递归复制对象的所有层,包括嵌套的对象或数组,以确保副本是完全独立的。
npm i lodash //安装依赖库
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()
方法来执行深拷贝。无论修改的是第一层元素还是嵌套的对象,都不会影响到原始对象。这是因为深拷贝递归地创建了每个对象的副本,确保了所有嵌套结构的独立性。
深拷贝的特点是创建一个原始对象的完全独立副本,不受原始对象或副本之间修改的相互影响。这在需要保持数据完整性和避免副作用的情况下非常有用。
深拷贝的实现方法有很多,以下是一些常见的深拷贝方式:
通过递归遍历对象或数组的所有层次,创建相应的副本。
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);
JSON.parse()
用于解析 JSON 字符串,将其转换为相应的 JavaScript 对象。
JSON.stringify()
用于将 JavaScript 对象转换为 JSON 字符串。
通过将对象转换为JSON字符串,然后再将其解析回对象,实现深拷贝。这种方法有一些限制,例如无法处理包含函数、RegExp等特殊对象的情况。
const ABC = { key1: 'value1', key2: { abc: 'abcValue' } };
const deepCopyObj = JSON.parse(JSON.stringify(ABC));
许多第三方库提供了深拷贝的方法,其中最常用的是 Lodash 库的 _.cloneDeep()
方法。
const _ = require('lodash');
const ABC = { key1: 'value1', key2: { abc: 'abcValue' } };
const deepCopyObj = _.cloneDeep(ABC);
在浏览器环境下,可以使用 MessageChannel
来创建对象的副本。
MessageChannel
是 HTML Living Standard 中定义的一种用于在不同上下文之间进行通信的 API。它主要用于在 Web 开发中实现跨文档、跨窗口、跨 iframe、跨文档对象模型 (DOM) 或者主线程和 Web Worker 之间进行异步消息传递。MessageChannel
创建了一个双向通信通道,通过两个相关联的 MessagePort
实例进行通信。
MessageChannel 的特点
MessageChannel
提供了两个 MessagePort
对象,分别命名为 port1
和 port2
,它们都可以用于发送和接收消息。postMessage()
方法,可以在一个端口上发送消息,而通过在另一个端口上监听 message
事件,可以接收消息。MessageChannel
通常用于传递一次性或大块的数据,例如,可以使用 Transferable
对象(例如,ArrayBuffer
)来传递大型数据结构,而无需复制数据。MessageChannel
进行通信。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等特殊对象的情况。在实际使用中,需要根据具体的需求选择最适合的深拷贝方式。