总结一下js的浅拷贝和深拷贝

news2025/1/24 8:32:08

js中对象的赋值是通过将一个对象的引用赋值给另一个变量,两个变量指向同一个内存地址。这意味着如果更改其中一个对象的值,另一个对象的值也会更改。

浅拷贝是将一个对象的值复制给另一个对象,但如果对象中包含对其他对象的引用,则这些引用仍然指向原来的对象。可以使用 Object.assign() 和 spread operator(扩展运算符 ...)等方法来实现浅拷贝。

深拷贝是完全复制一个对象及其中包含的所有对象。可以使用 JSON.parse(JSON.stringify()) 或 lodash.cloneDeep() 等方法来实现深拷贝。

一、浅拷贝

浅拷贝是将一个对象或数组的值复制给另一个对象或数组,但如果对象或数组中包含对其他对象或数组的引用,则这些引用仍然指向原来的对象或数组。

JavaScript 中浅拷贝可以通过如下几种方式实现:

  • 使用 Object.assign() 方法,该方法用于将一个或多个对象的属性复制到目标对象上。
let original = { a: 1, b: 2 };
let copied = Object.assign({}, original);
console.log(copied); // { a: 1, b: 2 }
  • 使用 Spread operator(...),可以直接将一个对象或数组拷贝到另一个对象或数组上。
let original = { a: 1, b: 2 };
let copied = { ...original };
console.log(copied); // { a: 1, b: 2 }
  • 使用 slice() 和 concat() 方法,可以将一个数组拷贝到另一个数组上。
let original = [1, 2, 3];
let copied = original.slice();
console.log(copied); // [1, 2, 3]
  • 使用 for...in 循环和 Object.keys() 或 Object.getOwnPropertyNames() 或 Object.getOwnPropertySymbols() 方法。
let original = { a: 1, b: 2 };
let copied = {};
Object.keys(original).forEach(key => {
  copied[key] = original[key];
});
console.log(copied); // { a: 1, b: 2 }

注意:所有上述方法都是实现浅拷贝,如果原对象或数组中包含对其他对象或数组的引用,则这些引用仍然指向原来的对象或数组。

二、深拷贝

深拷贝是指在拷贝过程中,拷贝一个对象中的所有数据,并创建一个新对象,对新对象进行操作并不会影响到原对象。

1、常规场景

JavaScript 中深拷贝可以通过如下几种方式实现:

  • 使用 JSON.parse(JSON.stringify(object)) 方法

需要注意的是:该方法会忽略 undefined 以及正则表达式类型的属性。

const A = { a: 7788, b: undefined, c: new RegExp(/-/ig) },
    B = JSON.parse(JSON.stringify(A));

console.log('A', A);
console.log('B', B);

  • 使用递归的方式,手动拷贝对象的每一层
function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    let copy;
    if (Array.isArray(obj)) {
        copy = [];
        for (let i = 0; i < obj.length; i++) {
            copy[i] = deepCopy(obj[i]);
        }
    } else {
        copy = {};
        for (let key in obj) {
            copy[key] = deepCopy(obj[key]);
        }
    }
    return copy;
}

const objA = { a: 123 },
    objB = { b: 456 };

// 浅拷贝
const objC = {...objA};
console.log('objA.a', objA.a);  // objA.a 123
console.log('objC.a', objA.a);  // objC.a 123
objC.a = 788;
console.log('objA.a', objA.a);  // objA.a 788
console.log('objC.a', objC.a);  // objC.a 788

// 深拷贝
const objD = deepCopy(objB);
console.log('objB.b', objB.b);  // objB.b 456
console.log('objD.b', objD.b);  // objD.b 456
objD.b = 899;
console.log('objB.b', objB.b);  // objB.b 456
console.log('objD.b', objD.b);  // objD.b 899

这个函数接受一个参数 obj,如果它不是对象或者是 null,那么直接返回该参数。如果它是数组,则创建一个新数组并递归复制每一项。否则,创建一个新对象并递归复制每一个属性。

  • 使用 lodash 类库的_.cloneDeep函数、 underscore 中的 _.clone() 函数等第三方库

2、特定场景一:内置对象类型的深拷贝

JavaScript 中复制内置对象类型(例如 Date,RegExp 等)的深拷贝可以使用特定的构造函数来重新创建该对象。

例如,对于 Date 对象,可以使用 new Date(originalDate.getTime()) 来创建一个新的日期对象,其中 originalDate.getTime() 返回原始日期对象的时间戳。

对于 RegExp 对象,可以使用 new RegExp(originalRegExp) 或 new RegExp(originalRegExp.source, originalRegExp.flags) 来创建一个新的正则表达式对象。

下面是一个使用构造函数来复制内置对象类型的深拷贝示例:


function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (obj instanceof Date) {
        copy = new Date(obj.getTime());
    } else if (obj instanceof RegExp) {
        copy = new RegExp(obj);
    } else if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

这个示例的深拷贝函数首先检查当前对象是否为内置对象类型,如果是,则使用相应的构造函数重新创建该对象,否则创建一个普通对象或数组。然后进行递归复制每一个属性。

需要注意的是,使用构造函数复制内置对象类型只适用于部分内置对象类型,对于其他的内置对象类型,可能需要使用其他的方法来进行复制,或者使用第三方库来进行复制。

总之,深拷贝复制内置对象类型需要考虑使用构造函数来重新创建对象,如果需要对这些对象进行深拷贝操作,可以使用上述方法或其他库来实现。

3、特定场景二:自定义对象类型的深拷贝

JavaScript 中自定义对象的深拷贝可以使用同样的递归方式实现,可以使用 WeakMap 方法。

function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (obj instanceof MyCustomObject) {
        copy = new MyCustomObject();
    } else if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

这个函数首先检查当前对象是否为自定义对象,如果是,则创建一个新的自定义对象,否则创建一个普通对象或数组。然后进行递归复制每一个属性。

注意,如果自定义对象中包含循环引用,需要使用 WeakMap 来避免出现死循环。

4、特定场景三:对象中存在函数或循环引用

对于函数,通常会忽略它们,因为函数不能被复制,而是需要重新定义。

对于循环引用,可以使用 WeakMap 来存储已经复制过的对象。每次遇到循环引用时,可以检查 WeakMap 中是否已经有该对象的副本,如果有,则直接使用副本,而不是重新创建。

function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

这是使用 WeakMap 的一种示例,这个示例的深拷贝函数递归地复制对象,但检查 WeakMap 中是否已经存在该对象的副本,如果存在则直接使用副本,而不是重新创建。

此外,使用 JSON.parse(JSON.stringify(obj)) 方法会自动忽略函数和循环引用,但是会忽略 undefined 以及正则表达式类型的属性。

还有,还可以使用第三方库,如 lodash 中的 _.cloneDeep() 函数、 underscore 中的 _.clone() 函数等来实现对象中存在函数或循环引用的深拷贝。

5、特定场景四:对象中有对其他对象的引用或者包含 Symbol 属性的对象

对于对象中有对其他对象的引用,可以使用 WeakMap 来存储已经复制过的对象。每次遇到对其他对象的引用时,可以检查 WeakMap 中是否已经有该对象的副本,如果有,则直接使用副本,而不是重新创建。

对于对象中包含 Symbol 属性的对象,可以使用 Object.getOwnPropertySymbols() 方法来获取该对象所有的 Symbol 属性,然后使用 Object.getOwnPropertyDescriptor() 方法来获取这些 Symbol 属性的值,最后将这些值赋给新对象。


function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    let symbols = Object.getOwnPropertySymbols(obj);
    symbols.forEach(symbol => {
        let descriptor = Object.getOwnPropertyDescriptor(obj, symbol);
        Object.defineProperty(copy, symbol, descriptor);
    });
    return copy;
}

这是使用 WeakMap 和 Symbol 属性的一种示例,这个示例的深拷贝函数首先检查 WeakMap 中是否已经存在该对象的副本,如果存在则直接使用副本,而不是重新创建。然后使用 Object.getOwnPropertySymbols() 方法获取该对象所有的 Symbol 属性,最后使用 Object.getOwnPropertyDescriptor() 方法获取这些 Symbol 属性的值,并将这些值赋给新对象。

这种方法可以保证深拷贝对象中包含的所有属性,包括对其他对象的引用和 Symbol 属性,但还是不能复制内置对象类型,这些对象类型是不可枚举的。

三、循环引用

JavaScript 中的循环引用指的是两个或多个对象之间相互引用的情况。这种情况通常发生在将一个对象赋给另一个对象的属性时,同时还将另一个对象赋给第一个对象的属性。

以下是一个示例:

let obj1 = {};
let obj2 = {};

obj1.prop = obj2;
obj2.prop = obj1;

这样就会产生一个循环引用,因为 obj1 和 obj2 相互引用。

循环引用可能导致 JavaScript 引擎无法正确处理内存,并导致内存泄漏。因此,在编写 JavaScript 代码时需要特别注意避免循环引用。如果您需要在两个对象之间建立关系,可以使用弱引用来避免循环引用。

在深拷贝中遇到循环引用就会导致死循环,因此需要使用特殊的算法来解决这个问题。可以使用递归算法和深度优先遍历来实现深拷贝,在遍历过程中跟踪已经遍历过的对象,如果遇到循环引用就直接返回已经遍历过的对象的引用。

总的来说,在使用浅拷贝和深拷贝时,需要根据需求和对象的结构来进行选择。通常来说,如果需要对对象进行修改并且不希望对原对象造成影响,那么应该使用深拷贝。如果只是需要读取对象中的数据而不需要修改,那么可以使用浅拷贝。在实现深拷贝时,需要特别注意循环引用和特殊属性问题。

 

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

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

相关文章

Linux虚拟网络设备---之Veth pair详解

本文目录 1、我们可以用以下命令来创建veth pair: veth0----veth12、创建二个命名空间namespaces后&#xff0c;可以用以下命令将二个veth设备分别移入二个命名空间ns0和ns1&#xff0c;并将它们连接起来。12、或者用以下命令在创建namespaces后&#xff0c;直接在二个namespac…

设备树的引入及简明教程

首先说明&#xff0c;设备树不可能用来写驱动。 设备树只是用来给内核里的驱动程序&#xff0c;指定硬件的信息。比如LED驱动&#xff0c;在内核的驱动程序里去操作寄存器&#xff0c;但是操作哪一个引脚&#xff1f;这由设备树指定。 需要编写设备树文件(dts: device tree s…

【协议】NVMe over RoCE |nvmeof

什么是nvme nvme ssd和普通ssd区别 ssd是固态硬盘&#xff0c;普通的ssd配的是SATA口&#xff08;AHCI协议&#xff09;&#xff0c;nvme ssd配的是PCIe口&#xff08;nvme传输协议&#xff09; 相比普通SSD的SATA口&#xff0c;nvme的PCIe口有巨大的性能优势。 更多详情见&…

HTTP超详细教程

1&#xff0c;HTTP协议 1.1&#xff0c;HTTP简述 HTTP全称为超文本传输协议&#xff0c;是一种应用比较广泛的应用层协议。 那何为超文本&#xff1f; 超文本指的是传输的内容不仅仅是文本&#xff0c;比如 html&#xff0c;css&#xff0c;javaScript 等数据&#xff0c;还…

SQL使用技巧

1、行列转换&#xff1a; decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值); select decode(sign(变量1-变量2),-1,变量1,变量2) from dual; --取较小值 sign()函数根据某个值是0、正数还是负数&#xff0c;分别返回0、1、-1 例如: 变量110&#xff0c;变量220 则s…

中间件定义

中间件(middleware)是基础软件的一大类&#xff0c;属于可复用的软件范畴。中间件在操作系统软件&#xff0c;网络和数据库之上&#xff0c;应用软件之下&#xff0c;总的作用是为处于自己上层的应用软件提供运行于开发的环境&#xff0c;帮助用户灵活、高效的开发和集成复杂的…

CTFHub | 读取源代码

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习&#xff0c;实训平台。提供优质的赛事及学习服务&#xff0c;拥有完善的题目环境及配套 writeup &#xff0c;降低 CTF 学习入门门槛&#xff0c;快速帮助选手成长&#xff0c;跟随主流比赛潮流。 0x01 题目描述…

互斥量实现原理探究

文章目录 1. 如何实现线程的加锁和解锁2. 封装一个锁3. 可重入和线程安全3.1 可重入与线程安全联系3.2 可重入与线程安全区别 4. 常见锁概念4.1 死锁4.2 代码实现4.3 死锁四个必要条件 1. 如何实现线程的加锁和解锁 经过上一篇文章的例子&#xff0c;大家已经意识到单纯的 i 或…

快速在linux上配置python3.x的环境以及可能报错的解决方案(python其它版本可同样方式安装)

一. linux安装python3.x 下面案例是安装python3.9 步骤&#xff0c;也可以指定其他版本安装 步骤1&#xff1a;安装系统依赖&#xff08;重要&#xff09; 这一步不执行&#xff0c;后面各种错误。 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sql…

【Python小技巧】更换python版本解决了plt.show()不显示图像的问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、df.plot() 显示不出图像&#xff1f;二、换个python版本问题解决总结 前言 from matplotlib import pyplot as plt kdata.plot(xtrade_time, y[close,BOLL…

ThreadPoolExecutor线程池

文章目录 一、ThreadPool线程池状态二、ThreadPoolExecutor构造方法三、Executors3.1 固定大小线程池3.2 带缓冲线程池3.3 单线程线程池 四、ThreadPoolExecutor4.1 execute(Runnable task)方法使用4.2 submit()方法4.3 invokeAll()4.4 invokeAny()4.5 shutdown()4.6 shutdownN…

SpringBoot-Actuator健康检查-打印日志改造应用策略模式+简单工厂

类图 包结构 代码实例 pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apac…

【MySQL 数据库】10、MySQL 的触发器

MySQL 的触发器 零、存储函数一、触发器二、触发器的使用和语法 零、存储函数 存储函数是有返回值的存储过程存储函数的参数只能是 IN 类型 characteristic 说明&#xff1a; ① DETERMINISTIC&#xff1a;相同的输入参数总是产生相同的结果 ② NO SQL &#xff1a;不包含 SQL…

【PCB专题】案例:绕等长怎么直接以颜色区分看出是否绕好

PCB上对于时序的处理,在板卡上实际我们是通过绕等长的手段。做为一个合格的Layout工程师,等长的处理是不可或缺的技能。 一般来说,在绕等长的时候我们可以使用Delay Tune命令来改变走线的长度,然后通过规则管理器中分析看看哪根线长哪根线短。 但是在实际工作中,很可能绕着…

【AI绘画】为小白准备的最简单本地部署安装使用教程——webui启动器

什么是AI绘画&#xff1f; ai绘画&#xff0c;也叫“ai作画”、“人工智能绘画”&#xff0c;即通过 AI 生成技术得到画作或图片。ai作画由来已久&#xff0c;有许多创作ai绘画作品的方式&#xff0c;包括基于规则的图像生成算法、深度学习算法。最近火爆全网的是通过文本描述…

悟道3.0全面开源!LeCun VS Max 智源大会最新演讲

夕小瑶科技说 原创 作者 | 小戏 2023 年智源大会如期召开&#xff01; 这场汇集了 Geoffery Hinton、Yann LeCun、姚期智、Joseph Sifakis、Sam Altman、Russell 等一众几乎是 AI 领域学界业界“半壁江山”的大佬们的学术盛会&#xff0c;聚焦 AI 领域的前沿问题&#xff0c…

【EasyX】实时时钟

目录 实时时钟1. 绘制静态秒针2. 秒针的转动3. 根据实际时间转动4. 添加时针和分针5. 添加表盘刻度 实时时钟 本博客介绍利用EasyX实现一个实时钟表的小程序&#xff0c;同时学习时间函数的使用。 本文源码可从github获取 1. 绘制静态秒针 第一步定义钟表的中心坐标center&a…

使用Python绘制粽子消消乐,素描图(优化版,正常/漫画/写实风格),词云图,字符画图及提取轮廓

使用Python绘制粽子消消乐&#xff0c;素描图&#xff08;优化版&#xff0c;正常/漫画/写实风格&#xff09;&#xff0c;词云图&#xff0c;字符画图及提取轮廓 1. 效果图2. 源码2.1 素描图源码2.2 [优化版&#xff1a;制作不同风格的素描图&#xff08;正常&#xff0c;漫画…

String的理解

1.号 1. 1 号连接符的实现原理 StringBuilder&#xff08;或者StringBuffer&#xff09;的apend方法拼接&#xff0c;然后toString方法返回新的字符串 1.2 号的特殊情况 1.2.1 当""两端均为编译期确定的字符串常量时&#xff0c;编译器会进行相应的优化&#xf…

springboot项目外卖管理 day05-新增与删除套餐

文章目录 一、新增菜品1.1、需求分析1.2、数据模型setmealsetmeal_dish 1.3、代码开发-梳理交互过程1.3.1、下拉框展示1.3.2、菜品窗口展示1.3.3、新增套餐 2、套餐分页查询 一、新增菜品 1.1、需求分析 套餐就是菜品的集合。 后台系统中可以管理套餐信息&#xff0c;通过新…