JavaScript 中的拷贝分为两种:浅拷贝和深拷贝。

一、浅拷贝

浅拷贝是指在拷贝过程中,只拷贝一个对象中的指针,而不拷贝实际的数据。所以,浅拷贝中修改新对象中的数据时,原对象中的数据也会被改变。

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

  • 使用结构赋值的方式,例如 let newObject = {...oldObject}
  • 使用 Object.assign() 方法,例如 let newObject = Object.assign({}, oldObject)

二、深拷贝

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

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 浅拷贝和深拷贝的更多相关文章

  1. JS面试题-<变量和类型>-JavaScript浅拷贝与深拷贝

    前言 最开始了解到深浅拷贝是因为准备面试,但那个时候因为在学校做的项目比较少需求也比较简单,所以没有在项目中遇到这类问题,所以对这个问题就属于知道这个知识点,看过相关内容,却没有自己的总结,也没有深入 ...

  2. Javascript 浅拷贝与深拷贝

    在了解JS的浅拷贝与深拷贝之前,我们需要先知道什么是值传递与引用传递. 在JS中,基本类型值的拷贝是按值传递的,而引用类型值的拷贝则是按引用传递的.通过值传递的变量间不会有任何牵连,互相独立:但是引用 ...

  3. 浅谈Javascript 浅拷贝和深拷贝的理解

    javascript中存储对象都是存地址的. 浅拷贝:浅拷贝是都指向同一块内存区块,浅拷贝共用同一内存地址,你改值我也变.如果拷贝的对象里面的值是一个对象或者数组,它就是浅拷贝,拷贝的知识引用地址.  ...

  4. javascript浅拷贝和深拷贝

    /* 浅拷贝 */ function extend(parent, child) { var i; child = child || {}; for (i in parent) { if (paren ...

  5. 关于JavaScript的浅拷贝和深拷贝

    在 JS 中有一些基本类型像是Number.String.Boolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他 ...

  6. Javascript中的浅拷贝和深拷贝

    很多开发语言中都有浅拷贝和深拷贝的说法,这里简单区分一下它们在Javascript中的区别,以及jQuery中深拷贝的实现. 在谈浅拷贝和深拷贝之前,先要屡清楚Javascript中的按值访问和按引用 ...

  7. javascript篇-浅拷贝与深拷贝

    理解javascript 的浅拷贝与深拷贝,首先看一下js的数据类型: js有5种基本数据类型:undefined,null,boolean,number,string 还有一种复杂的数据类型(也叫引 ...

  8. javascript浅拷贝深拷贝理解记录

    javascript的深拷贝和浅拷贝问题几乎是面试必问的问题.好记性不如烂笔头,特此来记录一下自己对深拷贝浅拷贝的理解. 顾名思义,拷贝就是copy复制,在js中可以浅而理解为对一个对象或者数组的复制 ...

  9. javascript中的浅拷贝和深拷贝(拷贝引用和拷贝实例)

    作者:千锋教育链接:https://www.zhihu.com/question/23031215/answer/326129003来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  10. JavaScript 数据结构与算法之美 - 栈内存与堆内存 、浅拷贝与深拷贝

    前言 想写好前端,先练好内功. 栈内存与堆内存 .浅拷贝与深拷贝,可以说是前端程序员的内功,要知其然,知其所以然. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScri ...

随机推荐

  1. grpc错误处理

    0.1.索引 https://waterflow.link/articles/1665938704477 我们都知道当发起http请求的时候,服务端会返回一些http状态码,不管是成功还是失败.客户端 ...

  2. ROS2时间同步(python)

    最近1周一直研究ROS2的时间同步,翻越很多博客,很少有人使用ROS2进行时间同步的代码,无奈不断尝试与源码阅读,终于将其搞定, 为此,本博客将介绍基于python的ROS2的时间同步方法. 本博客内 ...

  3. Landau-Vishkin

    基础算法 假设我们有两个字符串:,每个字符串由A C G T四个字母组成. 在两个字符串上,都有三种可能的编辑操作(突变): 删除某个字符 在某个位置插入字符 改变某个字符 每一个编辑操作都有惩罚值. ...

  4. CH58X/CH57X/V208的Broadcaster(广播者)例程讲解

    在对ble进行应用的时候,每个用户的需求可能不尽相同.这里着重介绍从机Broadcaster例程,只广播不连接. 使用该例程时可以在手机使用APP上对Broadcaster进行调试. 安卓端在应用市场 ...

  5. shardingsphere-jdbc 水平分表学习记录

    放在自己博客里搬过来一份~ 前司使用的是自己魔改的TDDL,在家时间比较多就尝试学一些业内比较常用的中间件. 这里记录一下学习中遇到的一些问题. 环境 设置的比较简单(太懒了就测试了几个表), 两个分 ...

  6. 工作中,本人常用到的unzip、zip命令

    1. 命令安装 1.1 zip安装 yum install zip 1.2 unzip安装 yum install unzip 2. 常用命令 2.1 常用zip命令 2.1.1 压缩文件 zip x ...

  7. 如何通过Java导出带格式的 Excel 数据到 Word 表格

    在Word中制作报表时,我们经常需要将Excel中的数据复制粘贴到Word中,这样则可以直接在Word文档中查看数据而无需打开另一个Excel文件.但是如果表格比较长,内容就会存在一定程度的丢失,无法 ...

  8. Docker | 专栏文章整理🎉🎉

    Docker Docker系列文章基本已经更新完毕,这是我从去年的学习笔记中整理出来的. 笔记稍微有点杂乱.随意,把它们整理成文章花费了不少力气.整理的过程也是我的一个再次学习的过程,同时也是为了方便 ...

  9. Optional对象

    Optional对象 Optional 类是一个可以为null的容器对象,用于简化Java中对空值的判断处理,以防止出现各种空指针异常. 静态方法-of 必须确定对象不为null 在使用of封装成op ...

  10. Go语言核心36讲17

    在前面的文章中,我们已经提到过很多次"指针"了,你应该已经比较熟悉了.不过,我们那时大多指的是指针类型及其对应的指针值,今天我们讲的则是更为深入的内容. 让我们先来复习一下. ty ...