【面试题】 JavaScript 中的深浅拷贝: 原理与实现

简介: 【面试题】 JavaScript 中的深浅拷贝: 原理与实现

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

前言

  在开发过程中,我们经常会遇到需要复制一个对象或数组的情况。在 JavaScript 中,我们可以使用浅拷贝或深拷贝来实现复制功能。浅拷贝只会复制对象或数组的第一层属性,如果属性的值还是对象或数组,那么它们之间的引用关系并不会改变。相反,深拷贝会完全复制对象或数组的所有属性,并创建新的引用关系。
  在什么情况下需要使用深拷贝呢?通常来说,当我们希望对象或数组的改变不影响原对象或数组时,就需要使用深拷贝。例如,我们可能希望在处理数据的过程中保留原始数据的副本,或者在修改一个对象的属性时不影响原对象。在本文中,我们将介绍 JavaScript 中的深拷贝方法,并给出使用深拷贝的示例代码。
复制代码

1.深浅拷贝究竟是什么东西???

  在 JavaScript 中,对象和数组都是引用类型,它们的值是存储在内存中的地址,而不是实际的值。当我们复制一个对象或数组时,如果我们只是复制它们的地址,那么原来的对象或数组和新的对象或数组都指向同一个地址,对其中一个对象或数组的修改会影响另一个对象或数组。这种复制方式称为浅拷贝。为了避免这种情况,我们需要进行深拷贝,即复制对象或数组的值,而不是复制地址。
复制代码

2.深拷贝的实现

 深拷贝的方法有很多种,常见的方法包括使用 `JSON.parse` 和 `JSON.stringify`、使用递归算法、使用 lodash 等。
复制代码

2.1 使用JSON.parseJSON.stringify 进行深拷贝

使用 JSON.parseJSON.stringify 进行深拷贝的方法非常简单,只需要先将要拷贝的对象或数组使用 JSON.stringify 转化为字符串,然后再使用 JSON.parse 将字符串转回原来的对象或数组即可。

下面是一个简单的示例,展示了如何使用 JSON.parseJSON.stringify 进行深拷贝:

const original = { a: 1, b: { c: 2 } };
// 使用 JSON.stringify 将对象转化为字符串,再使用 JSON.parse 将字符串转回对象
const copy = JSON.parse(JSON.stringify(original));
console.log(original === copy); // false
console.log(original.b === copy.b); // false
复制代码

在这个示例中,我们先定义了一个包含嵌套对象的对象 original,然后使用 JSON.parseJSON.stringify 进行深拷贝,得到了新的对象 copy。最后,我们使用 === 运算符比较了两个对象是否相等,以及两个对象的嵌套对象是否相等,发现了两个对象和两个嵌套对象都不相等,说明进行了深拷贝。

2.2 使用递归算法实现深拷贝

  使用 `JSON.parse` 和 `JSON.stringify` 进行深拷贝也有一些限制,比如不能复制函数、Symbol 等类型的值,也不能正确地复制循环引用的对象。
  因此,在实际应用中,我们还需要考虑使用其他的深拷贝方法来解决这些限制,例如递归算法实现深拷贝,它可以递归地复制对象或数组的值,并判断是否为基本类型(如数字、字符串、布尔值等),如果是基本类型就直接返回,否则就继续递归复制。
复制代码

下面是一个使用递归算法实现深拷贝的示例:

function deepCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  // 判断是数组还是对象
  const isArray = Array.isArray(obj);
  const copy = isArray ? [] : {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepCopy(obj[key]);
    }
  }
  return copy;
}
const original = { a: 1, b: { c: 2 } };
const copy = deepCopy(original);
console.log(original === copy); // false
console.log(original.b === copy.b); // false
复制代码

在这个示例中,我们定义了一个名为 deepCopy 的函数,它接受一个对象或数组作为参数,并进行深拷贝。首先,我们使用 typeof 运算符判断传入的参数是否为对象或数组,如果不是就直接返回。然后,我们使用 Array.isArray 函数判断传入的参数是数组还是对象,并创建新的数组或对象。最后,我们使用循环遍历对象或数组的属性,并使用递归调用 deepCopy 函数来复制属性的值。

使用递归算法实现深拷贝的方法相对来说比较复杂,但是它有一个明显的优势,就是可以正确地复制函数、Symbol 等类型的值,以及循环引用的对象。

2.2 使用深拷贝时该注意什么?

  1. 与原始数据的类型相关的问题

在使用深拷贝时,我们要注意,有些类型的数据在被深拷贝后可能会发生变化。例如,使用 JSON.parse()JSON.stringify() 函数时,会把函数转换成字符串,而使用递归函数时,会把正则表达式转换成空对象。

  1. 与浅拷贝相关的问题

在某些情况下,我们希望对象或数组的某些属性不被深拷贝,而是使用浅拷贝。这可以通过自定义递归函数来实现。例如,可以设置一个黑名单,表示哪些属性不需要深拷贝。

3. 浅拷贝的实现

浅拷贝是指在复制对象或数组时,只复制它的第一层属性,而不会复制它的属性的值(如果属性的值还是对象或数组的话)。因此,浅拷贝得到的新对象或数组与原对象或数组之间仍然存在引用关系

在 JavaScript 中,我们可以使用下面几种方法来实现浅拷贝:

  • 使用展开运算符(...):let newArray = [...oldArray]let newObject = {...oldObject}
  • 使用 Object.assign() 函数:let newObject = Object.assign({}, oldObject)
  • 使用数组的 slice() 函数或对象的 Object.keys() 函数:let newArray = oldArray.slice()let newObject = Object.keys(oldObject).reduce((acc, key) => ({...acc, [key]: oldObject[key]}), {})

3.1 浅拷贝的应用场景

1.   与深拷贝相比,浅拷贝的实现方法要简单得多,但是它的功能也相对较弱。如果我们希望复制对象或数组的所有属性,并创建新的引用关系,还是需要使用深拷贝,但是在某些情况下,我们可能希望使用浅拷贝。
2. 复制代码
  • 当我们需要构建一个新的对象或数组时,可能希望使用浅拷贝。因为浅拷贝只复制第一层属性,所以可以节省拷贝的时间和空间。例如,我们可以使用浅拷贝来创建一个新的数组,其中包含原数组的所有元素,但是不包含原数组的任何属性。
  • 当我们希望复制对象或数组的部分属性时,也可以使用浅拷贝。例如,我们可以使用展开运算符或 Object.assign() 函数来复制对象的某些属性。这可以节省拷贝的时间和空间,因为我们只复制了需要的属性。

总之,浅拷贝在一些特定的场景下非常有用,但是它的功能也相对较弱。我们应该根据实际情况选择使用浅拷贝

3.2 浅拷贝的实现方法

下面我们列举几种常见的方法:

  1. 使用展开运算符(...):
function shallowClone(obj) {
  return {...obj};
}
复制代码
  1. 使用 Object.assign() 函数:
function shallowClone(obj) {
  return Object.assign({}, obj);
}
复制代码
  1. 使用内置构造函数:
function shallowClone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  let clone = new obj.constructor();
  for (let key in obj) {
    clone[key] = obj[key];
  }
  return clone;
}
let obj = {a: 1, b: 2, c: [3, 4, 5]};
let shallow = shallowClone(obj);
console.log(shallow); // {a: 1, b: 2, c: [3, 4, 5]}
复制代码

4.总结

最后,在使用深浅拷贝时,我们要根据实际的需求来决定使用哪种方法,并确保复制的对象或数组能够满足我们的需求。
复制代码

如有帮助,麻烦点个赞,如有错误请指出,我是CoderBug,一个跟你一样追风的少年!

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  web前端面试题库 VS java后端面试题库大全

相关文章
|
11月前
|
消息中间件 存储 缓存
大厂面试高频:Kafka 工作原理 ( 详细图解 )
本文详细解析了 Kafka 的核心架构和实现原理,消息中间件是亿级互联网架构的基石,大厂面试高频,非常重要,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:Kafka 工作原理 ( 详细图解 )
|
4月前
|
机器学习/深度学习 JavaScript 前端开发
JS进阶教程:递归函数原理与篇例解析
通过对这些代码示例的学习,我们已经了解了递归的原理以及递归在JS中的应用方法。递归虽然有着理论升华,但弄清它的核心思想并不难。举个随手可见的例子,火影鸣人做的影分身,你看到的都是同一个鸣人,但他们的行为却能在全局产生影响,这不就是递归吗?雾里看花,透过其间你或许已经深入了递归的魅力之中。
188 19
|
10月前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
8月前
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
359 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
7月前
|
JavaScript 前端开发 Java
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
Array.find() 是 JavaScript 数组方法中一个非常实用和强大的工具。它不仅提供了简洁的查找操作,还具有性能上的独特优势:返回的引用能够直接影响原数组的数据内容,使得数据更新更加高效。通过各种场景的展示,我们可以看到 Array.find() 在更新、条件查找和嵌套结构查找等场景中的广泛应用。 在实际开发中,掌握 Array.find() 的特性和使用技巧,可以让代码更加简洁高效,特别是在需要直接修改原数据内容的情形。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
7月前
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
8月前
|
存储 NoSQL 前端开发
美团面试:手机扫描PC二维码登录,底层原理和完整流程是什么?
45岁老架构师尼恩详细梳理了手机扫码登录的完整流程,帮助大家在面试中脱颖而出。该过程分为三个阶段:待扫描阶段、已扫描待确认阶段和已确认阶段。更多技术圣经系列PDF及详细内容,请关注【技术自由圈】获取。
|
7月前
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
10月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)

热门文章

最新文章