JS对象拷贝的几种实现方法以及如何深拷贝(面试题)

server/2025/2/12 1:03:01/

前言

js中的对象深拷贝在项目开发中较常用到,本文介绍一下Js对象拷贝的几种实现方法,以及如何深拷贝

一、浅拷贝深拷贝


  浅拷贝是创建一个新对象,这个对象有着原始对象属性值的拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的是引用内存地址。可以称之为拷贝了,但没完全拷贝。

深拷贝是对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域来存放新对象,所以修改新对象不会影响原对象。如果不进行深拷贝,其中一个对象改变了浅拷贝所拷贝的引用值内部的属性,就会影响到另一个对象的属性值。

二、几种拷贝方式

1. JSON.parse(JSON.stringify(obj))

let a = {name: 'Jack',age: 18,hobbit: ['sing', {type: 'sports', value: 'run'}],score: {math: 'A',},run: function() {}, // 无法拷贝walk: undefined, // 无法拷贝fly: NaN, // --> nullcy: null,date: new Date() // date 字符串
}
let b = JSON.parse(JSON.stringify(a))

此种方式可以深拷贝一个对象,但是这个对象的内容得符合一定的限制要求,才能真正的深拷贝而没有遗漏或拷贝偏差。下面列举这个方法的缺陷。

缺陷:

   取不到值为 undefined 的 key;
   如果对象里有函数,函数无法被拷贝下来;
   无法拷贝 copyObj 对象原型链上的属性和方法;
   对象转变为 date 字符串;
   NaN 转变为 null。
   JSON 本来就不是专门为深拷贝而设计出来的,该方法的原理就是将对象先转换为 JSON 字符串,再从 JSON 字符串解析回 JavaScript 对象。JSON 所能保存的类型有限,转换为 JSON 字符串的过程中会按照 JSON 的一些规则处理,而无法保留原对象的所有细节。所以,如果深拷贝的应用场景无法接受这些细节的丢失,则不要使用这种方式深拷贝

2. Object.assign(target, source1, source2)

var data = {a: "123",b: 123,c: true,d: [43, 2],e: undefined,f: null,g: function() {    console.log("g");  },h: new Set([3, 2, null]),i: Symbol("fsd"),k: new Map([    ["name", "张三"],    ["title", "Author"]  ])};
var newData = Object.assign({},data)

这种方式一种浅拷贝方法。它只会复制对象的第一层属性,而不会复制对象内部的所有嵌套属性。

Object.assign方法作用是将 targetObj 和 sourceObj 合并,返回值是合并后的 targetObj 的引用,而这个过程只进行了浅拷贝

 3.普通递归函数实现深拷贝

function deepClone(source) {if (typeof source !== 'object' || source == null) {return source;}const target = Array.isArray(source) ? [] : {};for (const key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {if (typeof source[key] === 'object' && source[key] !== null) {target[key] = deepClone(source[key]);} else {target[key] = source[key];}}}return target;
}

解决循环引用和symblo类型

function cloneDeep(source, hash = new WeakMap()) {if (typeof source !== 'object' || source === null) {return source;}if (hash.has(source)) {return hash.get(source);}const target = Array.isArray(source) ? [] : {};Reflect.ownKeys(source).forEach(key => {const val = source[key];if (typeof val === 'object' && val != null) {target[key] = cloneDeep(val, hash);} else {target[key] = val;}})return target;
}

4. 迭代递归方法(解决闭环问题)

function deepCopy(data, hash = new WeakMap()) {let newData;if (typeof data === "object") {// nullif (data === null) {newData = data;}// Arrayelse if (Array.isArray(data)) {newData = [];data.forEach((item) => newData.push(deepClone(item, hash)));}// Dateelse if (data instanceof Date) {newData = new Date(data);}// regular expressionelse if (data instanceof RegExp) {newData = new RegExp(data);} else if (data instanceof Set) {// 实现set数据的深拷贝newData = new Set();Array.from(data).forEach((item) => newData.add(deepClone(item, hash)));} else if (data instanceof Map) {// 实现map数据的深拷贝newData = new Map();data.forEach((value, key) =>newData.set(deepClone(key, hash), deepClone(value, hash)));}// plain objectelse {// 用WeakMap的key保存原对象的引用记录, value是对应的深拷贝对象的引用// 例如: a:{b:{c:{d: null}}}, d=a, a 的深拷贝对象是 copy, 则 weakmap 里保存一条 a->copy 记录// 当递归拷贝到d, 发现d指向a,而a已经存在于weakmap,则让新d指向copyif (hash.has(data)) {newData = hash.get(data);} else {newData = {};hash.set(data, newData);for (let prop in data) {newData[prop] = deepClone(data[prop], hash);}}}}// 基本数据类型else {newData = data;}return newData;
}

5.第三方库lodash的cloneDeep()方法

这种方式是否使用,取决于我们项目中是否已使用过lodash其它功能,没必要为了一个深拷贝功能而引入一整个库。

import lodash from 'lodash'
let obj = {person: {name: '张三',age: 18,hobbies: ['跑步','乒乓球','爬山']},p: 1
}
const newObj = lodash.cloneDeep(obj)
obj.p = 20
console.log(newObj.p) // 输出1

初次调用deepCopy时,参数会创建一个WeakMap结构的对象,这种数据结构的特点之一是,存储键值对中的健必须是对象类型。

如果待拷贝对象中有属性也为对象时,则将该待拷贝对象存入weakMap中,此时的健是对该待拷贝对象的引用,值是拷贝结果对象的引用。然后递归调用该函数再次进入该函数,传入了上一个待拷贝对象的对象属性的引用和存储了上一个待拷贝对象引用的weakMap,因为如果是循环引用产生的闭环,那么这两个引用是指向相同的对象的,因此会进入if(hash.has())语句内,然后直接赋值return,退出函数,所以不会一直循环递归进栈,以此防止栈溢出。


http://www.ppmy.cn/server/166564.html

相关文章

【DeepSeek】DeepSeek概述 | 本地部署deepseek

目录 1 -> 概述 1.1 -> 技术特点 1.2 -> 模型发布 1.3 -> 应用领域 1.4 -> 优势与影响 2 -> 本地部署 2.1 -> 安装ollama 2.2 -> 部署deepseek-r1模型 1 -> 概述 DeepSeek是由中国的深度求索公司开发的一系列人工智能模型,以其…

对接DeepSeek

其实,整个对接过程很简单,就四步,获取key,找到接口文档,接口测试,代码对接。 获取 KEY https://platform.deepseek.com/transactions 直接付款就是了(现在官网暂停充值2025年2月7日&#xff0…

java项目之金华学校社团管理系统源码(ssm+mysql)

项目简介 金华学校社团管理系统实现了以下功能: 金华学校社团管理系统的主要使用者管理员对系统用户、公告信息进行管理。对社团信息进行管理,审核报名,统计社团报名结果等。学生维护个人信息,查看本校的社团信息,对…

蓝桥杯51单片机练习(国信长天比赛用)

文章目录 代码实现头文件固定模板延时函数HC138译码器和或非门流水灯闪烁次数(假设闪烁5次)从左向右依次亮从左向右依次灭 总代码 代码实现 头文件 #include <REGX52.H> 固定模板 void main() { while(1) { } } 延时函数 void Delay(unsigned char t) { while(t–…

[权限提升] Linux 提权 维持 — 系统错误配置提权 - 通配符(ws)注入提权

关注这个专栏的其他相关笔记&#xff1a;[内网安全] 内网渗透 - 学习手册-CSDN博客 0x01&#xff1a;通配符&#xff08;ws&#xff09;注入提权原理 通配符注入提权的核心是利用通配符的扩展特性&#xff0c;在命令执行时生成意外的参数或文件名&#xff0c;从而改变命令的行…

gitlab多项目流水线

背景是我有多个项目&#xff0c;希望其中一个项目被触发的时候&#xff0c;联动另外一个项目自动打包。然后我就看文档尝试操作了一下&#xff0c;所以有本文。 官方文档参考&#xff1a;https://gitlab.cn/docs/14.5/jh/ci/pipelines/multi_project_pipelines.html 不知道是不…

什么是企业经营驾驶舱

企业运营驾驶舱是一种基于数据可视化的管理工具&#xff0c;旨在帮助企业实时监控、分析和优化核心运营指标&#xff0c;辅助管理层快速决策。其概念类似于飞机驾驶舱&#xff0c;通过直观的仪表盘展示关键数据&#xff0c;让管理者“一目了然”掌握企业运营状态。数聚从多年的…

Kafka 无消息丢失最佳实战

1. 不要使用 producer.send(msg)&#xff0c;而要使用 producer.send(msg, callback)。记住&#xff0c;一 定要使用带有回调通知的 send 方法。 2. 设置 acks all。acks 是 Producer 的一个参数&#xff0c;代表了你对“已提交”消息的定义。 如果设置成 all&#xff0c;则…