JavaScript中的深拷贝与浅拷贝详解

news2024/10/7 16:16:22

在这里插入图片描述

🔥 个人主页:空白诗

在这里插入图片描述

文章目录

    • 📚 引言
    • ❓什么是拷贝
    • 🌟 浅拷贝:表面功夫
      • 浅拷贝常用方法
        • 1. 扩展运算符 (`...`)
        • 2. `Object.assign()`
        • 3. 手动遍历对象属性
    • 🌠 深拷贝:彻底分离
      • 深拷贝常用方法
        • 1. Lodash 的 `_.cloneDeep`
        • 2. JSON 的序列化与反序列化方法
        • 3. 小贴士
    • 📊 深拷贝与浅拷贝的抉择
    • 💯 面试考点
    • 🎯 总结

在这里插入图片描述

📚 引言

在JavaScript编程的奇妙之旅中,理解数据拷贝的机制是每位开发者绕不开的必修课。本文将带你深入浅出地探索深拷贝(Deep Copy)与浅拷贝(Shallow Copy)的概念、区别及其实现方法,让你的数据操作更加得心应手。🥳


❓什么是拷贝

「拷贝」,顾名思义,就是在计算机编程中创建一个已有对象或数据结构的副本的过程。根据拷贝的深度不同,可以分为 浅拷贝(Shallow Copy)深拷贝(Deep Copy) 两种类型,它们在实际应用中扮演着不同的角色

在这里插入图片描述

🌟 浅拷贝:表面功夫

「浅拷贝」仅涉及对象的第一层属性拷贝过程。当遇到嵌套的引用类型(比如对象、数组),浅拷贝并不会深入这些内部结构去创建新实例,而是直接复制这些内部对象的引用地址。这就意味着,尽管原对象与拷贝对象在表面上看似分离,实则在深层次结构上依旧紧密相连,彼此共享相同的数据稍有不慎的修改,就可能在二者之间产生意料之外的联动效果

实际作用

  1. 性能考虑:由于只复制一层,浅拷贝相对较快占用内存较少,适合于大型对象或深度嵌套结构中不需要完全独立的情况。
  2. 快速创建类似对象:当需要一个对象作为模板快速生成相似对象,但部分属性可能根据需要修改时,浅拷贝可以保留可共享的部分,仅修改差异部分。
  3. 资源节约:在某些场景下,如果确实不需要或不允许完全独立的副本,浅拷贝可以避免不必要的内存消耗。

下面通过一段代码来直观展现浅拷贝的特点:

const obj1 = {
	name : "Alice",
	age : 18
}
const obj2 = obj1 // obj2现在指向与obj1相同的内存地址
console.log("obj1",obj1) // 输出 obj1 { name: 'Alice', age: 18 }
console.log("---修改obj2的值后----") 
obj2.name = "Bob" // 修改了obj2指向的对象的name属性,由于obj1和obj2指向同一对象,所以这也会影响到obj1
console.log("obj1", obj1); // 输出 obj1 { name: 'Bob', age: 18 } -- obj1的值也确实发生了变化,因为它们共享同一对象
console.log("obj2", obj2); // 输出 obj2 { name: 'Bob', age: 18 } -- obj2的值同样为修改后的结果

在JavaScript中,当你将一个对象赋值给另一个变量时,实际上是将该对象的引用(内存地址)传递给了新变量,而不是创建了一个全新的对象副本。所以在上述代码中,obj2 = obj1意味着obj2obj1都指向了同一个对象在内存中的位置。

注释解释:

  • 当执行obj2.name = "Bob"时,实际上是在改变obj1obj2共同指向的那个对象的name属性值。因此,当你随后打印obj1时,会发现它的name属性也变成了"Bob"。
  • 这种现象体现了JavaScript中对象作为引用类型的特性,即变量存储的是对象的引用而非对象本身。因此,修改通过引用传递的对象会影响到所有指向该对象的变量。

浅拷贝常用方法

在JavaScript中实现浅拷贝有几种常用的方法,以下是其中的一些:

1. 扩展运算符 (...)
const originalObj = { a: 1, b: { c: 2 } };
const shallowCopyObj = { ...originalObj };
2. Object.assign()
const originalObj = { a: 1, b: { c: 2 } };
const shallowCopyObj = Object.assign({}, originalObj);
3. 手动遍历对象属性

可以通过循环遍历对象的键并手动复制它们到新的对象中,这种方法也可以实现浅拷贝。

function shallowCopy(obj) {
    let copy = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = obj[key];
        }
    }
    return copy;
}

const originalObj = { a: 1, b: { c: 2 } };
const shallowCopyObj = shallowCopy(originalObj);

请注意,以上所有方法都只能实现对象第一层级的浅拷贝,对于嵌套的对象或数组,它们内部的引用类型数据仍然是共享的。如果需要深拷贝(即完全复制包括嵌套结构在内的所有数据),则需要使用其他技术,如递归实现的深拷贝函数或者使用JSON.parse(JSON.stringify())方法(但这种方法有局限性,比如不能处理函数和循环引用)。


🌠 深拷贝:彻底分离

「深拷贝」致力于打造完全独立的数据,与浅拷贝形成鲜明对比。它通过递归遍历的方式,一丝不苟地复刻对象的每一层级,直至触及那些不可分割的基本类型(字符串、数字、布尔值等),为每一级都锻造出崭新的镜像。这种做法确保了无论原对象如何变迁,拷贝出的新对象都能傲然独立,不受丝毫干扰,实现了数据的彻底隔离与保护。💪

实际作用

  1. 数据隔离:在需要确保数据独立性,避免操作拷贝对象时影响原对象的场景下,深拷贝非常关键,例如在多线程编程或并行处理中。
  2. 安全复制敏感数据:在复制包含敏感信息的对象时,深拷贝可以确保源数据的安全,因为修改拷贝不影响源数据。
  3. 持久化存储:当需要将对象序列化后保存或通过网络传输时,通常会使用深拷贝来创建一个完整的、独立的副本,以保证数据的完整性

下面的示例展示了如何手工实现一个深拷贝函数,并通过一个简单的测试用例来验证其功能:

// 自定义深拷贝函数
function deepClone(obj) {
    // 基础情况判断:非对象或为null,直接返回
    if (typeof obj !== 'object' || obj === null) return obj;
    
    // 根据obj类型创建相应的新容器
    let clone = Array.isArray(obj) ? [] : {};
    
    // 遍历原对象的每个属性
    for (let key in obj) {
        // 确保不是原型链上的属性
        if (obj.hasOwnProperty(key)) {
            // 递归拷贝当前属性值(无论基本类型还是引用类型)
            clone[key] = deepClone(obj[key]);
        }
    }
    // 返回构建完毕的拷贝对象
    return clone;
}

// 示例
let original = { a: 1, b: { c: 2 } }; // 原始对象包含嵌套
let deepCopy = deepClone(original); // 执行深拷贝

// 修改原始对象中的嵌套对象属性
original.b.c = 3;

// 输出拷贝对象中对应属性,验证深拷贝效果
console.log(deepCopy.b.c); // 输出 2,显示了深拷贝的独立性

这段代码首先定义了deepClone函数,它能够递归地处理任何层级的嵌套结构,为每一步都生成全新的副本。通过对比修改前后deepCopy对象的属性值,我们明确观察到深拷贝带来的彻底分离效果:即使原对象发生改变,深拷贝出的对象依然维持着最初的状态,完美体现了深拷贝的核心价值。

深拷贝常用方法

在JavaScript的世界里,不必每次都亲力亲为地手动实现深拷贝,许多强大的库和内建方法能助你一臂之力,使代码更加简洁高效。

1. Lodash 的 _.cloneDeep

Lodash 是一个广泛使用的JavaScript实用库,提供了丰富的函数来简化日常开发。其中的_.cloneDeep函数就是专门用来执行深拷贝的。

安装 Lodash(如果项目中尚未安装):

npm install lodash

使用 _.cloneDeep 方法:

const _ = require('lodash');

let original = { a: 1, b: { c: 2 } };
let deepCopyByLodash = _.cloneDeep(original);
2. JSON 的序列化与反序列化方法

利用 JSON.stringify()JSON.parse() 进行深拷贝是一种快速简便的方法,适用于简单对象结构。但请注意,这种方法存在以下限制:

  • 函数丢失 🚫: JSON格式不支持函数,因此对象中的函数会被忽略。
  • 循环引用问题 🔁: 如果对象中存在循环引用,此方法会抛出错误。
  • 日期对象转换 ⏰: 日期对象会被转换为字符串,丢失其原始的日期类型。

示例代码:

let original = { a: 1, b: { c: 2 } };
let deepCopyByJSON = JSON.parse(JSON.stringify(original));

数据丢失情况:

const obj1 = {
	a:Symbol('a'),					// Symbol 会丢失
	b:undefined, 					// undefined 会丢失
	c:new Date(), 					// Date 转换成字符串形式
	d: new RegExp(/d/), 			// RegExp 转换成 {}
	e:NaN,							// NaN 会变成null
	f: function () {},				// 函数 function 会丢失
	g: new Map([['a',123]]), 		// Map 转换成 {}
	h: new Set([123]),  			// Set 转换成 {}
}
const obj2 = JSON.parse(JSON.stringify(obj1))
console.log('obj1',obj1)
console.log('obj2',obj2)

在这里插入图片描述

3. 小贴士
  • 在选择工具方法时,考虑你的具体需求。对于简单场景,JSON方法可能足够用;而对于复杂对象或需要保留函数的情况,Lodash的_.cloneDeep或其他专门的深拷贝库可能是更佳选择。
  • 虽然自动化工具提供了便利,了解其背后的工作原理及限制也同样重要,这能帮助你在遇到特殊情况时做出正确的决策。

总之,合理利用现有工具,可以让深拷贝这一任务变得轻松许多,同时也让代码更加专注在业务逻辑上。


📊 深拷贝与浅拷贝的抉择

在软件开发过程中,选择「深拷贝」还是「浅拷贝」,往往取决于特定的场景需求和权衡考虑:

  • 性能考量 🚦:
    深拷贝由于其递归复制所有层级的特性,确实会导致更高的CPU和内存使用。对于包含大量元素或深层次嵌套的对象,这可能会显著增加处理时间和空间开销。因此,在性能敏感的应用中,若非必要,可能倾向于避免频繁使用深拷贝。

  • 数据独立性 🔒:
    当应用程序逻辑要求两个对象之间必须保持完全独立,任何一方的修改都不应影响另一方时,深拷贝是必要的选择。这对于维护数据一致性、避免意外副作用至关重要,尤其是在并发操作或状态管理复杂的系统中。

  • 资源节省 💰:
    浅拷贝通过仅复制对象的引用而非实际值,能够快速高效地完成拷贝操作,特别适合于对象结构较为简单或在短期内不需要修改引用对象的场景。这种方式减少了内存占用,提升了程序效率,但需谨慎使用,以免因共享引用引发的意外修改。

在决定深拷贝与浅拷贝的策略时,开发者需要综合考虑当前操作的上下文环境、对象的复杂度、性能要求以及对数据隔离程度的需求。简而言之:

  • 若追求执行速度和资源优化,且能接受数据间的潜在相互影响,则倾向于浅拷贝。
  • 若强调数据的完整独立性和长期稳定性,哪怕牺牲部分性能,深拷贝则是更为安全可靠的选择。

正确权衡这些因素,将有助于编写既高效又健壮的代码。


💯 面试考点

面试题1:

问题: 什么是JavaScript中的浅拷贝?请给出一个示例说明浅拷贝可能导致的问题。

答案: 浅拷贝是指创建一个新对象,但这个新对象的属性如果是引用类型(如对象、数组等),则只会复制这些引用类型的地址,而不是创建它们的副本。这意味着原始对象和拷贝对象会共享这些引用类型的数据。如果修改拷贝对象中的引用类型属性,原始对象的相关属性也会受到影响。

示例:

let original = { name: "Alice", details: { age: 30 } };
let shallowCopy = Object.assign({}, original);

// 修改拷贝对象中的引用类型属性
shallowCopy.details.age = 35;

console.log(original.details.age); // 输出 35,说明原始对象被影响了

面试题2:

问题: 如何在JavaScript中实现一个简单的深拷贝?并讨论JSON.parse(JSON.stringify())方法作为深拷贝的局限性。

答案: 实现深拷贝的一种简单方法是使用JSON.parse()JSON.stringify(),但这有其局限性。例如,这种方法不能处理函数、RegExp、Date、undefined等特殊类型的对象,也不能处理循环引用的情况。

示例代码:

// 可行的方式
let original = { a: 1, b: { c: 2 } };
let deepCopyByJSON = JSON.parse(JSON.stringify(original));
console.log(original, deepCopyByJSON) // 输出一致 { a: 1, b: { c: 2 } }
// 无法处理的情况
const obj1 = {
	a:Symbol('a'),					// Symbol 会丢失
	b:undefined, 					// undefined 会丢失
	c:new Date(), 					// Date 转换成字符串形式
	d: new RegExp(/d/), 			// RegExp 转换成 {}
	e:NaN,							// NaN 会变成null
	f: function () {},				// 函数 function 会丢失
	g: new Map([['a',123]]), 		// Map 转换成 {}
	h: new Set([123]),  			// Set 转换成 {}
}
const obj2 = JSON.parse(JSON.stringify(obj1))
console.log('obj1',obj1) // obj1 { a: Symbol(a), b: undefined, c: 2024-05-09T15:51:21.907Z, d: /d/, e: NaN, f: [Function: f], g: Map(1) { 'a' => 123 }, h: Set(1) { 123 }}
console.log('obj2',obj2) // obj2 { c: '2024-05-09T15:51:21.907Z', d: {}, e: null, g: {}, h: {} }
// 循环引用出错的情况
let obj1 = {
  name: "Object 1"
};
let obj2 = {
  name: "Object 2"
};

// 形成循环引用
obj1.reference = obj2;
obj2.reference = obj1;

// 尝试使用 JSON.stringify() 进行深拷贝
try {
  let jsonString = JSON.stringify(obj1);
  let clonedObj = JSON.parse(jsonString);
  console.log("深拷贝成功:", clonedObj);
} catch (error) {
  console.error("深拷贝失败,原因:", error.message); // 循环引用会导致报错
}

面试题3:

问题: 描述一个实际应用场景,解释为什么在该场景下深拷贝比浅拷贝更合适。

答案: 假设你正在开发一个在线文档编辑器应用,用户可以打开一个文档模板进行编辑。每个用户应该能够独立编辑自己的版本,而不会影响到模板或其他用户的文档。在这种情况下,当你从模板创建一个新的文档时,你需要对模板进行深拷贝。

场景分析:

  • 浅拷贝会导致所有基于同一模板创建的文档实际上共享相同的对象引用,因此一个用户的编辑会影响到其他用户的文档内容。
  • 深拷贝则为每个新文档创建完全独立的副本,包括所有嵌套的对象和数组,这样每个用户可以自由编辑而不影响到模板或其他用户,保证了数据的隔离性和一致性。

因此,在这个场景下,深拷贝是更合适的选择,因为它确保了每个用户的工作环境是独立且安全的。


🎯 总结

在JavaScript的广阔天地里,深拷贝与浅拷贝是构建高效且健壮应用程序的基石。通过本次详尽的探讨,我们不仅理解了它们的基本概念,还深入分析了各自的优缺点及应用场景:

  • 浅拷贝如同表面的镜像,迅速且直接,适用于节省资源或快速复制简单结构,但需警惕潜在的共享引用风险。
  • 深拷贝则是彻底的复刻,确保数据的完全独立,虽在性能上有所牺牲,却为复杂逻辑和长期数据隔离提供了保障。

掌握了手动实现深拷贝的递归方法,同时也领略了利用现成工具(如Lodash的_.cloneDeep或JSON的序列化/反序列化)的便捷,这为我们提供了灵活多样的解决方案。

记住,没有绝对的好坏,只有最适合场景的选择。明智地根据项目需求和性能考量来决定使用浅拷贝还是深拷贝,是每个开发者必备的技能。


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1659400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows 10安装 docker desktop

升级 windows 10 windows 10 升级到 20H2,如 20H2 19045.4291。 注意:需返回更新,重启计算机,确保更新完整。 bios 开启虚拟化 开启cpu虚拟化功能。 windows 启用功能 启用hyper-v 启用 wsl 安装 wsl https://learn.microso…

大模型背后的秘密公式: Q*?

这麽说好像我是James Bond后面那个厉害的Q先生,Q是英文Quartermaster(軍需官)第1個英文字大寫,是007系列英國祕勤局虛構部門Q部門的領導。 Stanford大学的研究者最近发表了一篇名为"From r to Q*: Your Language Model is S…

react18【系列实用教程】JSX (2024最新版)

为什么要用 JSX? JSX 给 HTML 赋予了 JS 的编程能力 JSX 的本质 JSX 是 JavaScript 的语法扩展,浏览器本身不能识别,需要通过解析工具(如babel)解析之后才能在浏览器中运行。 bable 官网可以查看解析过程 JSX 的语法 …

(动画详解)LeetCode225.用队列实现栈

. - 力扣&#xff08;LeetCode&#xff09; 题目描述 解题思路 这道题的思路就是使用两个队列来实现 入栈就是入队列 出栈就是将非空队列的前n-1个元素移动到新的队列中去 再将最后一个元素弹出 动画详解 代码实现 #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.…

Mock.js 问题记录

文章目录 Mock.js 问题记录1. 浮点数范围限制对小数不起效2. increment 全局共用 Mock.js 问题记录 最新写网页的时候引入了 Mock.js 来生成模拟数据&#xff1b; Mock使用起来很方便&#xff0c;具体可以参考 官网 很快就能上手&#xff0c; 但是这个项目最近一次提交还是在2…

Android 开机启动模式源码分析

在机器关机情况下&#xff0c;长按Power键启动机器&#xff0c;如果这时机器低电&#xff0c;会提示低电&#xff0c;机器不会正常启动&#xff1a; 而代码如下&#xff1a; 如果不是低电&#xff0c;正常情况是可以启动的。 在关机情况下&#xff0c;插入USB&#xff0c;机…

Spark云计算平台Databricks使用,上传文件

Databricks&#xff0c;是属于 Spark 的商业化公司&#xff0c;由美国加州大学伯克利 AMP 实验室的 Spark 大数据处理系统多位创始人联合创立。Databricks 致力于提供基于 Spark 的云服务&#xff0c;可用于数据集成&#xff0c;数据管道等任务。 创建workspace&#xff1a;Spa…

AUS GLOBAL 再次荣登皇家贝蒂斯俱乐部官网

AUS GLOBAL 作为一家备受信赖的金融服务领导者&#xff0c;一直以来都在致力于为客户提供卓越的交易体验和专业的服务。再次登上皇家贝蒂斯俱乐部官网Banner&#xff0c;不仅是对我们过去合作的肯定&#xff0c;更是对未来合作的信心和期待。这标志着我们之间的合作更加稳固和成…

雷森托尔环保科技有限公司见证2024杭州数字供应链装备展潮流

参展企业介绍 青岛雷森托尔环保科技有限公司创建于2018年&#xff0c;位于山东青岛&#xff0c;现注册资本3000万。公司主营生产模压木托盘、化工木托盘、大型设备木包装、出口木托盘、酒柜木酒架等&#xff0c;公司拥有技术人员6人&#xff0c;均为包装设计专业毕业&#xff0…

计算机科学类SSCI期刊,高效录用,检索稳定!

今天老毕给大家分享一本偏向系统理论实践方向SSCI&#xff0c;JCR2 区&#xff0c;中科院大类社会学 4区&#xff0c;2022年的影响因子为1.9&#xff0c;五年影响因子为2.5。编辑有好&#xff0c;响应迅速。 &#x1f4d8; 基本信息 &#x1f4d6; ISSN 2079-8954 &#x…

美国商务部公布数字孪生技术投资计划

文章目录 前言一、主要内容二、相关背景‍‍‍‍前言 5月6日,美国商务部公布了一项价值2.85亿美元的投资计划,这项名为《美国芯片制造研究竞标》(CHIPS Manufacturing USA Institute Competition)的投资计划旨在向符合条件的申请者进行征求招标,协调建立和运营美国芯片制…

springboot整合websocket,超简单入门

springBoot整合webSocket&#xff0c;超简单入门 webSocket简洁 WebSocket 是一种基于 TCP 协议的全双工通信协议&#xff0c;它允许客户端和服务器之间建立持久的、双向的通信连接。相比传统的 HTTP 请求 - 响应模式&#xff0c;WebSocket 提供了实时、低延迟的数据传输能力。…

【JavaEE】博客系统(前端页面设计)

文章目录 一、预期效果二、实现博客列表页 一、预期效果 二、实现博客列表页 实现导航栏 编辑 blog_list.html, 创建导航栏的 html 代码. 导航栏里面包含 logo, 标题, 以及一些按钮(跳转链接). 为了实现左右排列, 在 logo 和 按钮 之间加一个 spacer 作为占位器. <!-- 导航…

FPGA采集卡,可实现CVBS/HDMI/SDI三种信号转换

可实现CVBS/HDMI/SDI三种信号转换&#xff0c; 客户应用:电视台&#xff0c;舞台&#xff0c;会议室 主要性能: 1:标准CVBS信号输入,标准HDMI信号输入,标准SDI信号输入,输入信号自适应. 2:3G/HD/SDSDI信号输出可选 2:1080P/10801/720P/4801/5761常用分辩率可选 1080PSF/720P30/…

SSC369G 双4K高性价比AI IPC方案

一、方案描述 SSC369G 双4K高性价比AI IPC方案采用主芯片SSC369G&#xff0c;内核为CA55四核最高主频为1.5Ghz处理器。SOC内置集成一个64位的四核RISC处理器&#xff0c;先进的图像信号处理器&#xff08;ISP&#xff09;&#xff0c;高性能的H.265/H.264/MJPEG视频编解码器&a…

LeetCode-2960. 统计已测试设备【数组 模拟】

LeetCode-2960. 统计已测试设备【数组 模拟】 题目描述&#xff1a;解题思路一&#xff1a;模拟解题思路二&#xff1a; 一次遍历&#xff0c;简洁写法解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个长度为 n 、下标从 0 开始的整数数组 batteryPercentages &#xf…

多模态大模型通过外接数据方案实现电力智能巡检(设计方案)

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

深度主动学习(Deep Active Learning)——基于pytorch和ALipy工具包实现双向GRU模型

前言 在ALipy的官网说ALipy只支持sklearn和tensorflow模型&#xff0c;模型对象应符合 scikit-learn api。 但是alipy提供了ToolBox的工具箱&#xff0c;里面包装了多种查询策略&#xff0c;计算指标等工具&#xff0c;几乎具有Alipy的全部功能&#xff0c;虽然不能使用ALipy提…

BLIP2预研笔记

0. 前言 文章是公司内部分享学习写的预研报告&#xff0c;里面有小部分文段是直接从网上借鉴的&#xff0c;侵删 1. 任务和方法历史进化&#xff1a; 在大模型等类似的预训练模型的方式&#xff08;以包含“预训练阶段”等n阶段训练方式为特色&#xff09;为主流之前&#xf…

太牛了!360大佬编写的《应急响应指导手册》火了!(PDF限时3天领取)

免责声明&#xff1a; 请使用者遵守《中华人民共和国网络安全法》&#xff0c;由于传播、利用本账号所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;公众号及作者不为此承担任何责任。 简介 这份《应急响应指导手册》&#xf…