深拷贝和浅拷贝的概念
深拷贝和浅拷贝是复制数据结构的两种不同方式。
浅拷贝是指创建一个新对象,但仅复制原对象的属性引用,而不是其属性值。即,如果原对象的属性值是对象或数组,浅拷贝后的新对象会引用相同的内存地址,因此修改内层对象会影响到原对象。(简单数据类型拷贝值,引用数据类型拷贝地址)
深拷贝是创建一个新的对象,并复制原始对象的所有属性,包括嵌套的对象和数组。这意味着在原始对象或新对象上的更改不会相互影响。
浅拷贝实现方法
使用 Object.assign()
javascript
// 原始对象
const original = {
name: 'Alice',
age: 30,
hobbies: ['reading', 'hiking']
};
// 使用 Object.assign() 创建一个浅拷贝
const copy = Object.assign({}, original);
// 修改拷贝对象的属性
copy.name = 'Bob';
copy.hobbies.push('cooking');
// 输出原始对象和拷贝对象
console.log('Original:', original); // Original: { name: 'Alice', age: 30, hobbies: ['reading', 'hiking', 'cooking'] }
console.log('Copy:', copy); // Copy: { name: 'Bob', age: 30, hobbies: ['reading', 'hiking', 'cooking'] }
在这个例子中,original
对象的 hobbies
数组被浅拷贝到 copy
对象中,所以修改 copy
对象的 hobbies
数组也会影响到 original
对象的 hobbies
数组。
使用扩展运算符(...
)
javascript
// 原始对象
const original = {
name: 'Alice',
age: 30,
hobbies: ['reading', 'hiking']
};
// 使用扩展运算符创建一个浅拷贝
const copy = { ...original };
// 修改拷贝对象的属性
copy.name = 'Bob';
copy.hobbies.push('cooking');
// 输出原始对象和拷贝对象
console.log('Original:', original); // Original: { name: 'Alice', age: 30, hobbies: ['reading', 'hiking', 'cooking'] }
console.log('Copy:', copy); // Copy: { name: 'Bob', age: 30, hobbies: ['reading', 'hiking', 'cooking'] }
类似于 Object.assign()
,扩展运算符创建的拷贝对象在遇到引用类型的属性时也只会复制其引用,这意味着修改数组或对象属性会同时影响原始对象。
深拷贝实现方法
ISON.parse()和JSON.stringify()方法
这是实现深拷贝的一种简单方式,但它有一些限制,例如不能复制函数和特殊对象(如 Date)。同时,它不能处理包含循环引用的对象。
以下是使用 JSON.parse() 和 JSON.stringify0 方法实现深拷贝的示例代码
javascript
const obj = { name: 'John', age: 30, address: { city: 'New York', state: 'NY' } };
const obj2 = JSON.parse(JSON.stringify(obj));
obj2.address.city = 'Chicago';
console.log(obj.address.city); // Output: New York
console.log(obj2.address.city); // Output: Chicago
相关名词解释------特殊对象
在 JavaScript 中,特殊对象包括一些具有特定功能或行为的内建对象:
Object
:所有对象的基类。Array
:表示有序的数据集合。Function
:表示函数,实际上也是对象。Date
:用于处理日期和时间。RegExp
:用于正则表达式匹配。Error
:用于错误处理,包含不同类型如TypeError
、SyntaxError
等。Map
:表示键值对的集合,键和值都可以是任何类型。Set
:表示值的集合,每个值只能出现一次。WeakMap
:类似于Map
,但键是弱引用,不阻止垃圾回收。WeakSet
:类似于Set
,但值是弱引用,不阻止垃圾回收。
下面详细介绍一下Date,Map,Set
Date
Date
是 JavaScript 中用于处理日期和时间的对象。如果你使用 JSON.stringify
将一个 Date
对象转换为 JSON 字符串,它会被转换为字符串格式的日期(通常是 ISO 格式)。例如:
javascript
const date = new Date();
const jsonString = JSON.stringify(date);
console.log(jsonString); // 输出日期字符串,例如: "2024-09-05T01:15:00.000Z"
当你使用 JSON.parse
解析这个字符串时,它会成为一个普通的字符串,而不是一个 Date
对象。这意味着你会丢失 Date
对象的原始类型和方法。因此,如果需要在深拷贝过程中保留 Date
对象,你可能需要手动处理这些特殊类型。
Map
Map
是 JavaScript 中一种数据结构,用于存储键值对。与普通对象不同的是,Map
的键可以是任意类型,包括对象、函数、原始类型等,且键值对的顺序会被记住。Map
对象还提供了一些便利的方法,比如 set()
、get()
、has()
和 delete()
,用于操作存储的键值对。
javascript
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 42);
map.set(3, 'value3');
console.log(map.get('key1')); // 输出: value1
console.log(map.get(3)); // 输出: value3
在 JSON 序列化中的处理
Map
对象不能直接被 JSON.stringify
方法正确序列化为 JSON 字符串。在使用 JSON.stringify
处理 Map
时,Map
会被转换为一个普通对象,丢失了其键值对的原始数据结构:
javascript
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
const jsonString = JSON.stringify([...map]);
console.log(jsonString); // 输出: [["key1","value1"],["key2","value2"]]
在这个例子中,Map
被转换成了一个包含键值对的二维数组,这样 JSON 字符串就可以表示 Map
中的内容。但这仅仅是转换为字符串的表示方式,解析出来的内容并不是一个 Map
对象,而是普通的数组。
解释说明
...map,前面的三个点指的是展开运算符。展开原因:JSON.stringify
不能直接处理 Map
对象,因此需要将 Map
转换为数组。这是因为 JSON.stringify
可以处理数组,但不能直接处理 Map
还原 Map
要将序列化后的 Map
数据还原为 Map
对象,你需要手动解析并重新构造它:
javascript
const jsonString = '[["key1","value1"],["key2","value2"]]';
const entries = JSON.parse(jsonString);
const map = new Map(entries);
console.log(map.get('key1')); // 输出: value1
console.log(map.get('key2')); // 输出: value2
这样,你可以从 JSON 字符串重新创建一个 Map
对象,并保留其键值对和结构。
Set
Set
是 JavaScript 中用于存储唯一值的数据结构。与数组类似,Set
也可以存储任意类型的值,但不同之处在于它不允许重复的元素。Set
还会保持插入元素的顺序,并提供了如 add()
、has()
、delete()
和 clear()
等方法来操作元素。
javascript
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 不会添加重复值
console.log(set.has(1)); // 输出: true
console.log(set.has(3)); // 输出: false
在 JSON 序列化中的处理
Set
对象不能直接被 JSON.stringify
方法正确序列化。若需要将 Set
转换为 JSON 字符串,通常需要将其转换为数组:
javascript
const set = new Set([1, 2, 3]);
const jsonString = JSON.stringify([...set]);
console.log(jsonString); // 输出: "[1,2,3]"
还原 Set
要从 JSON 字符串还原为 Set
对象,你需要手动解析和重新构造它:
javascript
const jsonString = "[1,2,3]";
const array = JSON.parse(jsonString);
const set = new Set(array);
console.log(set.has(1)); // 输出: true
console.log(set.has(4)); // 输出: false
这样,你就可以从 JSON 字符串恢复 Set
对象,并保留其中的唯一值。
相关名词解释------循环引用
循环引用的对象指的是对象之间相互引用,从而形成一个闭环。例如,假设对象 A 引用对象 B,而对象 B 又引用对象 A,这样它们就形成了一个循环引用。这种结构在序列化时可能会导致问题,比如 JSON.stringify() 会失败,因为它无法处理这种循环结构。
javascript
// 创建两个对象
const objA = {};
const objB = {};
// 使 objA 引用 objB
objA.reference = objB;
// 使 objB 引用 objA,形成循环引用
objB.reference = objA;
console.log(objA);
console.log(objB);
递归拷贝对象
这种方法涉及递归遍历对象,并创建一个新的对象,复制原始对象的属性,并在需要时递归复制嵌套的对象和数组。
这种方法可以处理循环引用,并复制函数和特殊对象。
以下是使用递归拷贝对象实现深拷贝的示例代码
javascript
function deepClone(obj) {
// 检查 obj 是否为对象且不为 null
if (typeof obj === 'object' && obj !== null) {
// 根据 obj 的类型创建一个新的对象或数组
let result = Array.isArray(obj) ? [] : {};
// 遍历 obj 的每一个属性
for (let key in obj) {
// 检查对象 obj 是否有 key 这个属性,并且这个属性是对象自身拥有的属性,而不是从原型链上继承来的属性
if (obj.hasOwnProperty(key)) {
// 对属性值进行递归深拷贝,并将其赋值给 result 的对应属性
result[key] = deepClone(obj[key]);
}
}
// 返回深拷贝的结果
return result;
}
// 如果 obj 不是对象或是 null,则直接返回原始值
return obj;
}
const obj = { name: 'John', age: 30, address: { city: 'New York', state: 'NY' } };
const obj2 = deepClone(obj);
obj2.address.city = 'Chicago';
console.log(obj.address.city); // Output: New York
console.log(obj2.address.city); // Output: Chicago
深拷贝的缺点是它可能会更加耗时,因为需要递归遍历整个对象图,复制所有属性和嵌套的对象和数组。而浅拷贝是一种更快的复制方式,因为它只复制了对象的引用。在某些情况下,浅拷贝可能会更有效。
lodash库
使用 lodash
库可以方便地进行深拷贝操作。lodash
提供了一个名为 cloneDeep
的函数,它能够递归地拷贝对象及其嵌套的属性,从而确保深拷贝。
下面是一个使用 lodash
的 cloneDeep
函数进行深拷贝的示例:
首先,确保你已经安装了 lodash
。如果还没有安装,可以使用 npm 或 yarn 安装:
javascript
npm install lodash
# 或者
yarn add lodash
然后,你可以在代码中使用 cloneDeep
来进行深拷贝:
javascript
// 引入 lodash 库
const _ = require('lodash');
// 原始对象
const original = {
name: 'Alice',
age: 30,
hobbies: ['reading', 'hiking'],
address: {
city: 'Wonderland',
postalCode: '12345'
}
};
// 使用 lodash 的 cloneDeep() 创建一个深拷贝
const deepCopy = _.cloneDeep(original);
// 修改拷贝对象的属性
deepCopy.name = 'Bob';
deepCopy.hobbies.push('cooking');
deepCopy.address.city = 'Neverland';
// 输出原始对象和拷贝对象
console.log('Original:', original);
// Original: { name: 'Alice', age: 30, hobbies: ['reading', 'hiking'], address: { city: 'Wonderland', postalCode: '12345' } }
console.log('Deep Copy:', deepCopy);
// Deep Copy: { name: 'Bob', age: 30, hobbies: ['reading', 'hiking', 'cooking'], address: { city: 'Neverland', postalCode: '12345' } }