JavaScript前端面试题——深拷贝及浅拷贝

深拷贝和浅拷贝的概念

深拷贝和浅拷贝是复制数据结构的两种不同方式。

浅拷贝是指创建一个新对象,但仅复制原对象的属性引用,而不是其属性值。即,如果原对象的属性值是对象或数组,浅拷贝后的新对象会引用相同的内存地址,因此修改内层对象会影响到原对象。(简单数据类型拷贝值,引用数据类型拷贝地址)

深拷贝是创建一个新的对象,并复制原始对象的所有属性,包括嵌套的对象和数组。这意味着在原始对象或新对象上的更改不会相互影响。

浅拷贝实现方法

使用 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 中,特殊对象包括一些具有特定功能或行为的内建对象:

  1. Object:所有对象的基类。
  2. Array:表示有序的数据集合。
  3. Function:表示函数,实际上也是对象。
  4. Date:用于处理日期和时间。
  5. RegExp:用于正则表达式匹配。
  6. Error :用于错误处理,包含不同类型如 TypeErrorSyntaxError 等。
  7. Map:表示键值对的集合,键和值都可以是任何类型。
  8. Set:表示值的集合,每个值只能出现一次。
  9. WeakMap :类似于 Map,但键是弱引用,不阻止垃圾回收。
  10. 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 的函数,它能够递归地拷贝对象及其嵌套的属性,从而确保深拷贝。

下面是一个使用 lodashcloneDeep 函数进行深拷贝的示例:

首先,确保你已经安装了 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' } }
相关推荐
一个小坑货3 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet277 分钟前
【Rust练习】22.HashMap
开发语言·后端·rust
古月居GYH7 分钟前
在C++上实现反射用法
java·开发语言·c++
Myli_ing31 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
在下不上天33 分钟前
Flume日志采集系统的部署,实现flume负载均衡,flume故障恢复
大数据·开发语言·python
陌小呆^O^1 小时前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp
dr李四维1 小时前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
I_Am_Me_1 小时前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
雯0609~1 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
重生之我是数学王子1 小时前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt