面试题:JS 中怎么实现深克隆和浅克隆

面试题:JS 中怎么实现深克隆和浅克隆

一、深克隆和浅克隆

1. 克隆的研究对象

克隆(拷贝)就是创建一份数据的副本,其分为深克隆和浅克隆两种实现方式。对于原始类型的值而言,深克隆和浅克隆没有任何区别,因此我们关于克隆的研究对象是对象类型

2. 两种克隆的区别

  • 深克隆:深克隆会对原始对象进行无限层级的复制,会递归地复制原始对象中的所有属性,从而创建一个独立于原始对象的全新副本。修改深克隆后的对象中的数据,不会影响到原始对象。
  • 浅克隆:浅克隆会对原始对象进行一层复制,会复制原始对象中的所有属性,但对于嵌套对象或数组,只会复制引用。修改浅克隆后的对象中的数据,会影响到原始对象。

注意:直接将对象赋值给一个变量不算浅拷贝。浅拷贝的结果和原数据至少在 === 的判断下是 false

二、浅克隆的实现方式

1. Object.assign(对象)

  • 语法Object.assign(target, source1, source2, ...)

    • 功能:将一个或多个源对象(source)的可枚举属性复制到目标对象(target)。
    • 参数
      • target 目标对象,用于接收源对象的属性。
      • source 源对象,其属性会被复制到目标对象。
    • 返回值:目标对象(target),其包含了源对象中的所有属性。
    • 注意事项
      • 对于目标对象中的嵌套对象或数组属性,Object.assign 方法只会复制它们的引用,而不会创建全新副本。因此如果修改嵌套对象或数组中的内容,目标对象和源对象都会受到影响
      • 如果 targetsource1source2... 中存在同名属性,则以后边对象的属性值为准,这称之为属性覆盖
  • 浅克隆的实现const target = Object.assign({}, source);

    const source = {name: "chuanyitu",gender: "male",info: {country: "China",position: "North"}
    }const target = Object.assign({}, source); // 实现了浅拷贝console.log(source === target); // false
    console.log(source.info === target.info); // true 说明只复制了引用
    

2. array.slice(数组)

  • 语法array.slice([begin[, end]])

    • 功能:提取数组的一部分([begin, end)),作为一个新数组返回。
    • 参数
      • begin:提取元素的开始索引(包括该索引对应元素)。如果省略该参数,则默认从索引 0 开始。如果该参数为负数,则表示从数组末尾相应位置开始。
      • end:提取元素的结束位置(不包括对应元素)。如果省略该参数,则默认一直提取到数组最后一个元素。如果该参数为负数,则表示从数组末尾相应位置结束。
    • 返回值:一个新的数组,包含 [begin, end) 的数组元素。
    • 注意事项:对于数组中的对象或数组类型的元素,.slice 方法只会复制它们的引用,而不会创建全新副本。
  • 浅克隆的实现const target = source.slice();

    const source = ["chuanyitu", "male", { country: "China", position: "North" }];const target = source.slice(); // 实现了浅拷贝console.log(source ===  target); // false
    console.log(source[2] === target[2]); // true 说明只复制了引用
    

3. array.concat(数组)

  • 语法array.concat(source1[, source2[, source3[, ...]]])

    • 功能:将一个或多个数组或元素和当前数组合并,作为一个新数组返回。原数组不会受到影响。
    • 参数source 如果该参数是一个元素,则将其添加到新数组中。如果该参数是一个数组,则将其中的元素提取出来,添加到新数组中。
    • 返回值:一个新的数组,包含 array 中的所有元素,以及 source 中的元素(作为元素添加其本身,作为数组添加其中的元素)。
    • 注意事项:对于数组中的对象或数组类型的元素,.concat 方法只会复制它们的引用,而不会创建全新副本。
  • 浅克隆的实现const target = [].concat(source);

    const source = ["chuanyitu", "male", { country: "China", position: "North" }];const target = [].concat(source); // 实现了浅拷贝console.log(source ===  target); // false
    console.log(source[2] === target[2]); // true 说明只复制了引用
    

4. Array.from(数组)

  • 语法Array.from(arrayLike/iterableObjects[, mapFn[, thisArg]])

    • 功能:基于类数组对象或可迭代对象创建一个数组。
      • 类数组对象:具有 length 属性和索引元素的对象,如 Stringarguments 等。
      • 可迭代对象:如 SetMap 等。
    • 参数
      • arrayLike/iterableObjects:类数组或可迭代对象。
      • mapFn:映射函数,用于对每个元素进行处理,生成新的数组元素。
      • thisArg:表示 mapFn 函数中 this 指向。
    • 返回值:由类数组对象或可迭代对象创建的新的数组对象。
    • 注意事项:对于对象中的嵌套对象或数组Array.from 方法只会复制它们的引用,而不会创建全新副本。
  • 浅克隆的实现const target = Array.from(source);

    const source = ["chuanyitu", "male", { country: "China", position: "North" }];const target = Array.from(source); // 实现了浅拷贝console.log(source ===  target); // false
    console.log(source[2] === target[2]); // true 说明只复制了引用
    

5. 展开运算符(对象或数组)

  • 我们也可以使用展开运算符实现浅克隆,而且对数组和对象都生效!!!

    Object.assign 对象生效

    array.slicearray.concatArray.from 数组生效

  • 浅克隆的实现(对象)const target = {...source};

    const source = {name: "chuanyitu",gender: "male",info: {country: "China",position: "North"}
    }const target = {...source}; // 实现了浅拷贝console.log(source === target); // false
    console.log(source.info === target.info); // true 说明只复制了引用
    
  • 浅克隆的实现(数组)const target = [...source];

    const source = ["chuanyitu", "male", { country: "China", position: "North" }];const target = [...source]; // 实现了浅拷贝console.log(source === target); // false
    console.log(source[2] === target[2]); // true 说明只复制了引用
    

三、深克隆的实现方式

0. 深克隆要考虑的逻辑细节

  • 对于原始类型 => 直接返回

  • 对于循环引用 => 使用 Map 或 WeakMap 记录已经处理过的对象,避免无限递归(建议使用 WeakMap,建立弱引用关系)

  • 对于函数 => 直接返回

  • 对于 Date 对象 => 使用 Date 构造函数重新创建一个日期对象

  • 对于 RegExp 对象 => 使用 RegExp 构造函数重新创建一个正则对象

  • 对于 Array 对象 => 递归克隆

  • 对于 Map 对象 => 递归克隆

  • 对于 Set 对象 => 递归克隆

  • 对于一般对象 => 递归克隆

  • 对于对象或数组 => 递归克隆

    • Symbol 作为键名的处理方式 => 使用 Reflect.ownKeys()

      Reflect.ownKeys() 可以获取对象的所有属性(可枚举、不可枚举、符号属性)

      Object.keys() 只能获取对象的可枚举属性

  • 对于 DOM 元素 => 直接返回

1. JSON.stringify & JSON.parse

  • 深克隆的实现

    const source = {name: "chuanyitu",gender: "male",info: {country: "China",position: "North"}
    }const target = JSON.parse(JSON.stringify(source)); // 实现了深克隆console.log(source === target); // false
    console.log(source.info === target.info); // false 说明实现了深克隆
    
  • 存在的问题

    • 序列化会自动忽略 undefinedSymbol、函数

      const source = {a: undefined,b: Symbol(),c: () => { }
      }const target = JSON.parse(JSON.stringify(source));console.log(source === target); // false
      console.log(source); // { a: undefined, b: Symbol(), c: [Function: c] }
      console.log(target); // {}
      
    • 序列化会NaNInfinity-Infinity 转换为 null

      const source = {a: NaN,b: Infinity,c: -Infinity
      }const target = JSON.parse(JSON.stringify(source));console.log(source === target); // false
      console.log(source); // { a: NaN, b: Infinity, c: -Infinity }
      console.log(target); // { a: null, b: null, c: null }
      
    • 无法解决循环引用的问题

      const student = {name:"chuanyitu",
      }const school = {title:"XDU",
      }student.school = school;
      school.student = student;const studentCopy = JSON.parse(JSON.stringify(student));
      /* TypeError: Converting circular structure to JSON */
      

2. 手写一个深克隆

2.1 原始类型逻辑
if (source === null || typeof source !== 'object') {return source;
}
2.2 DOM 元素逻辑
if (source instanceof Node) {return source;
}
2.3 循环引用逻辑
if (hash.get(source)) {return hash.get(source);
}
2.4 Date 对象逻辑
if (source instanceof Date) {return new Date(source);
}
2.5 RegExp 对象逻辑
if (source instanceof RegExp) {return new RegExp(source);
}
2.6 Function 对象逻辑
if (source instanceof Function) {return source;
}
2.7 Map 对象逻辑
if (source instanceof Map) {const clonedMap = new Map();source.forEach((value, key) => {clonedMap.set(deepClone(key, hash), deepClone(value, hash));});hash.set(source, clonedMap);return clonedMap;
}
2.8 Set 对象逻辑
if (source instanceof Set) {const clonedSet = new Set();source.forEach(value => {clonedSet.add(deepClone(value, hash));});hash.set(source, clonedSet);return clonedSet;
}
2.9 一般对象逻辑
/* source 是一般对象或数组 */
const clonedSource = new source.constructor(); // 创建一个新的对象或数组
// Reflect.ownKeys(obj) 相当于 Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(target))
Reflect.ownKeys(source).forEach(key => {clonedSource[key] = deepClone(source[key], hash);
})
hash.set(source, clonedSource);
return clonedSource;
2.10 简单实现
function deepClone(source, hash = new WeakMap()) {/* hash 是额外开辟的一个存储空间,用来存储 “当前对象-拷贝对象” 的映射关系,用于解决循环引用问题。如果 hash.get(source) 查找到内容,说明当前对象已经被拷贝过了,直接返回拷贝结果即可,从而避免循环调用;如果 hash.get(source) 未查找到内容,说明当前对象没有被拷贝过,此时可以将拷贝结果添加到内存空间中去 *//* 这里使用 WeakMap 开辟内存空间,可以配合垃圾回收机制,防止内存泄漏。因为其键名所指向的对象,不计入一次引用,从而不影响垃圾回收机制 *//* source 是原始类型 */if (source === null || typeof source !== 'object') {return source;}/* source 时 DOM 元素 */if (source instanceof Node) {return source;}/* 处理循环引用情形 */if (hash.get(source)) {return hash.get(source);}/* source 是 Date 对象 */if (source instanceof Date) {return new Date(source);}/* source 是 RegExp 对象 */if (source instanceof RegExp) {return new RegExp(source);}/* source 是 Function 对象 */if (source instanceof Function) {return source;}/* source 是 Map 对象 */if (source instanceof Map) {const clonedMap = new Map();source.forEach((value, key) => {clonedMap.set(deepClone(key, hash), deepClone(value, hash));});hash.set(source, clonedMap);return clonedMap;}/* source 是 Set 对象 */if (source instanceof Set) {const clonedSet = new Set();source.forEach(value => {clonedSet.add(deepClone(value, hash));});hash.set(source, clonedSet);return clonedSet;}/* source 是一般对象或数组 */const clonedSource = new source.constructor(); // 创建一个新的对象或数组// Reflect.ownKeys(obj) 相当于 Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(target))Reflect.ownKeys(source).forEach(key => {clonedSource[key] = deepClone(source[key], hash);})hash.set(source, clonedSource);return clonedSource;
}

3. structuredClone

  • 介绍structuredClone 是一个用于实现深度克隆对象的 JS 内置 API,其可以克隆绝大多数 JS 对象。此外,该 API 还能处理循环引用问题。

    不足之处:structuredClone 不能克隆函数、DOM 节点、Error 对象等;同时,浏览器支持有限,并不是所有浏览器环境都支持该 API。

  • 深克隆的实现

    const source = {name: "chuanyitu",age: 23,details: {hobbies: ["reading", "gaming"],address: { country: "China" }},date: new Date(),regex: /test/i,map: new Map([["key1", "value1"]]),set: new Set([1, 2, 3])
    };// 使用 structuredClone 进行深拷贝
    const target = structuredClone(source);// 验证深拷贝效果
    console.log(target.details === source.details); // false
    console.log(target.date === source.date); // false
    console.log(target.regex === source.regex); // false
    console.log(target.map === source.map); target // false
    console.log(target.set === source.set); // false
    

REFERENCES

https://juejin.cn/post/7072528644739956773#heading-10

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

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

相关文章

Cy5.5-甘氨鹅去氧胆酸荧光染料标记物:一种生物成像工具

在现代生物研究和医学诊断领域,荧光染料标记物扮演着举足轻重的角色。它们能够实现对生物体内特定分子或细胞的非侵入性、实时追踪,从而为我们揭示生命过程的奥秘提供工具。 Cy5.5-甘氨鹅去氧胆酸荧光染料标记物的结构与特性 Cy5.5-甘氨鹅去氧胆酸荧光染…

水库大坝安全监测系统打通监控数据“最后一公里”

一、概述 我国有水库8万座左右,其中土石坝多数,病险水库占水库也很多。众所周知,水库在防洪、兴利上具有重要的调节作用,如何保证水库安全,及合理有效的利用水资源,是水利建设者需要探讨的主要内容。科学技…

【Python字符串攻略】:玩转文字,编织程序的叙事艺术

文章目录 🚀一.字符串基础🌈二.查看数据类型⭐三.转化❤️四.字符串索引🚲五.字符串切片🎬六.字符串切片-步长☔七.反向切片注意事项🚲八.字符串💥查💥改💥删 ❤️九.字符串拼接&…

强化用户登录接口:解决登录接口被攻击导致掉线卡顿!

一、引言 用户登录接口是任何Web应用的核心部分,它负责身份验证和授权流程。然而,这些接口也常常成为黑客攻击的目标,尤其是当涉及到动态请求处理时。动态请求通常指的是根据用户输入生成的请求,这为诸如SQL注入、XSS攻击和CSRF攻…

Ansys Mechanical|使用CABLE280和LINK180单元建立线缆模型

一. CABLE280和LINK180单元都可以用于此分析。它们都可以用来划分梁实体。下面是这两种单元的特性。 CABLE280单元 适用于仅分析单向拉伸场景,比如线缆 不包括剪切变形影响结果 每个节点有三个自由度:Ux,Uy,Uz 与属…

【Qt】 new成功,但是没有进入到构造函数。

NameTest工程中 nametest.cpp NameTest::NameTest() {pdata new privateAB; }NameTest::~NameTest() {if (pdata){privateAB *p (privateAB *)pData; //void *pdata nullptr;delete p;pdata nullptr;} }内部类: privateAB #include "private.h"#i…

消息队列的 6 种经典使用场景和 Kafka 架构设计原理详细解析

今天来聊一聊 Kafka 消息队列的使用场景和核心架构实现原理,帮助你全面了解 Kafka 其内部工作原理和设计理念。。 Apache Kafka 是一个高吞吐量、分布式的流处理平台,广泛应用于实时数据管道和流处理应用中。 Kafka 以其高性能、低延迟、扩展性和可靠性…

进口单座调节阀的特点

进口单座调节阀的特点可以归纳为以下几点: 高精度控制: 采用单座阀结构,能够实现高精度的流量和压力控制,满足工业生产过程中对流量精度的要求。泄漏量小,通常小于阀额定容量的0.01%,符合ANSI B16.104-197…

Vue+Django上传文件

Vue部分&#xff0c;使用el-upload组件 <!--action必须要有&#xff0c;但是通过其他按钮触发&#xff0c;不通过submit()触发--> <!--accept限制上传文件类型--> <!--file-list绑定文件变量--> <el-uploadaction"":auto-upload"false&qu…

Android14 WMS-窗口绘制之relayoutWindow流程(二)-Server端

本文接着如下文章往下讲 Android14 WMS-窗口绘制之relayoutWindow流程(一)-Client端-CSDN博客 然后就到了Server端WMS的核心实现方法relayoutWindow里 WindowManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server…

任务3.5 清洗网址中的垃圾字符

本实战任务聚焦于数据清洗在Java编程中的应用&#xff0c;特别是清洗网址中的垃圾字符。数据清洗是确保数据质量的重要环节&#xff0c;它帮助开发者去除数据中的异常、错误或无关字符&#xff0c;从而提高数据分析的准确性和有效性。 任务背景&#xff1a;理解数据清洗的重要性…

刷代码随想录有感(93):贪心算法——无重叠区间(区间重叠问题:求区间重叠次数)

题干: 代码&#xff1a; class Solution { public:static bool cmp(vector<int>& a, vector<int>& b){return a[0] < b[0];}int eraseOverlapIntervals(vector<vector<int>>& intervals) {sort(intervals.begin(), intervals.end(), c…

vulnhub靶机实战_DC-2

下载 靶机下载链接汇总&#xff1a;https://download.vulnhub.com/使用搜索功能&#xff0c;搜索dc类型的靶机即可。本次实战使用的靶机是&#xff1a;DC-2下载链接&#xff1a;https://download.vulnhub.com/dc/DC-2.zip 启动 下载完成后&#xff0c;打开VMware软件&#xf…

Python怎么变成白色:深入剖析Python的“变色”之旅

Python怎么变成白色&#xff1a;深入剖析Python的“变色”之旅 在编程的世界里&#xff0c;Python以其简洁、易读的特点赢得了众多开发者的喜爱。然而&#xff0c;当你说“Python怎么变成白色”时&#xff0c;可能让人有些摸不着头脑。这里的“白色”似乎并不是指Python语言本…

BitBlt 和 StretchBlt 使用举例

当使用BitBlt和StretchBlt函数时&#xff0c;你需要指定源设备上下文&#xff08;通常是一个包含位图的内存设备上下文&#xff09;和目标设备上下文&#xff08;例如&#xff0c;屏幕的设备上下文&#xff09;。以下是这两个函数的使用举例&#xff1a; 一、使用BitBlt BitB…

SendGrid发送邮件时如何调用API接口群发?

SendGrid发送邮件模板如何定制&#xff1f;邮件发送限制有哪些&#xff1f; SendGrid发送邮件是一种方便快捷的方式&#xff0c;可以在应用程序或网站中轻松地发送大量邮件。通过调用SendGrid的API接口&#xff0c;您可以实现群发邮件&#xff0c;无论是通知用户、发送营销邮件…

HDFS文件块损坏处理方案

1、问题概述 flume采集文本文件存储到hdfs中hive的ods层目录,并在hive中通过msck repair table刷新元数据,加载文本文件。报错如下: 2、问题分析 文件块BP-531411289-172.31.57.12-1539657748238出现了未知异常,导致namenode不能获取该文件块的信息,该文件块是由flume采…

小程序开发平台——搭建全功能小程序商城功能 前后端分离 带完整的安装代码包以及搭建教程

系统概述 随着电子商务的蓬勃发展&#xff0c;越来越多的企业和商家希望拥有自己的线上商城&#xff0c;以拓展销售渠道和提升用户体验。然而&#xff0c;传统的商城开发方式往往成本高、周期长&#xff0c;且难以满足快速变化的市场需求。因此&#xff0c;我们致力于打造一款…

【x264】码率控制模块的简单分析—帧级码控策略

【x264】码率控制模块的简单分析—帧级码控策略 1.码率控制模式2.恒定量化参数&#xff08;Constant Quantization Parameter, CQP&#xff09;2.1 CQP初测2.2 CQP的实现2.3 CQP存在的问题 3.恒定质量因子&#xff08;Constant Ratefactor, CRF&#xff09;3.1 CRF初测3.2 CRF的…