Javascript中的对象拷贝(对象复制/克隆)

Javascript中的对象拷贝(对象复制/克隆)

Jack Lee 的 CSDN 博客
邮箱 :291148484@163.com
CSDN 主页https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
本文地址https://blog.csdn.net/qq_28550263/article/details/117751704

提示
可以直接使用工具模块 @jcstdio/jc-utilscopy 进行深拷贝或浅拷贝。
安装:

npm install @jcstdio/jc-utils

使用:

import { copy } from '@jcstdio/jc-utils'

// 浅拷贝
const obj1 = {a: 1, b: {c: 2}};
const shallowCopyObj1 = copy.shallowCopy(obj1);
console.log(shallowCopyObj1); // {a: 1, b: {c: 2}}
console.log(shallowCopyObj1.b === obj1.b); // true

// 使用 deepCopy 方法深拷贝 (递归复制嵌套对象实现)
const obj2 = {a: 1, b: {c: 2}};
const deepCopyObj2 = copy.deepCopy(obj2);
console.log(deepCopyObj2); // {a: 1, b: {c: 2}}
console.log(deepCopyObj2.b === obj2.b); // false

// 使用 serializeCopy 方法深拷贝 (使用JSON序列化和反序列化实现)
const obj3 = {a: 1, b: {c: 2}};
const serializeCopyObj3 = copy.serializeCopy(obj3);
console.log(serializeCopyObj3); // {a: 1, b: {c: 2}}
console.log(serializeCopyObj3.b === obj3.b); // false

其中:@jcstdio/jc-utils 是一个用于 NodeJS、浏览器 中的编程工具模块。可参考博文 《JavaScript/TypeScript 编程工具集-@jcstdio/jc-utils 模块》 了解更多。


目 录

1. 对象的引用

2. 浅拷贝

3. 深拷贝


1. 对象的引用

要说“拷贝”还要先说“引用”的概念。
在JavaScript中没有“指针”的概念,但保留了对象的“引用”。首先必须明确,与“Java”、“Python”等经典面向对象编程语言中“一切皆可对象”不同,在JavaScript中绝非一切皆是对象,并且“引用”是“对象”的引用。这里就需要区分在赋值操作“=”的右侧,到底是一个“字面量”还是一个对象。举例而言:

var a = 1;
var b = "Hello World!"

var c = a

这里的数字1和"Hello World!"都并非对象,而是不可变的字面量值。他们分别源于简单基本类型numberstring而不是内置对象NumberString。(不过,对于左侧的变量,如果你要使用一些方法或属性,如.leng,并不需要显式将string转为String对象)

由于右侧不是对象,这在赋值操作var c = a传递的是值,即将number 1传给变量c,换成var c = b也类似。

而下例中:

var d = {
  Hello : 'world'
};

var e = d;

由于变量d的右侧是一个变量,这时var d = {Hello : 'world'};使得变量d创建了一个引用,即使得d中保存了右侧那个对象的地址,我们可以对变量d间接对右侧那个变量进行操作,如d.Hello。而后一个赋值操作var e = d;传递的是引用的内容,由于第一个赋值使得d引用到了其右侧那个对象的内存地址,后一个赋值只不过是将变量d中所存储的内容地址赋值了一份给变量e而已,因此这时变量ed引用到了同一个对象。

2. 浅拷贝

浅拷贝,又称“浅层拷贝”、“浅复制”、“浅层复制”等等。
一个很常见的通俗说法,“浅层拷贝就是只拷贝一层”,这样的说法其实不太准确。
浅拷贝的本质特点是:

  • 拷贝原对象后将得到一个新对象;
  • 新对象的将中的所有属性都是原对象中对应属性的一个引用

因此从表象上看,浅拷贝拷贝出来的新对象中所有属性的值会复制原对象中对应属性的值。例如:

var obj_a = {
  is_right : true
}

var obj_b == {
 a : 1,
 b : obj_a
}

如果获取对象obj_b的浅拷贝得到一个新对象,可以使用ES6提供的Object.assign()方法:

var new_obj = Object.assign({}, obj_b);

其中Object.assign()方法,第一个参数是目标对象,后面为一个或多个源对象。
该方法对obj_b实现浅拷贝的过程为,遍历一个或多个源对象(从第二个参数开始)所有可枚举的自有键,并逐个进行“=”完成复制,最后返回目标对象。
上例中,对于键值对“a:1”,由于数字“1”是一个不可变的值(字面量)而非数字,new_obj的第一个键值对完全相同,故有:

new_obj.a == 1   // true

然而第二个键值对的值obj_a引用了一个对象,进行“=”复制到new_obj的键值时,传递的时引用的地址,这样使得new_obj对应与键b的值引用到了对象obj_a。即必有:

new_obj.b === obj_a

那么,为什么称浅层拷贝呢?
以上我们已经了解了这种拷贝方法的本质,但是如果想要了解这个名字的由来,我们不妨假设上例中,将obj_a修改为一个键值中含有对象的对象。如:

var obj_c = {
are_you_ok : true
}

var obj_a = {
  is_right : true,
  is_ok : obj_c
}

var obj_b == {
 a : 1,
 b : obj_a
}

这时对 obj_b 进行浅拷贝得到new_obj后,new_obj.is_ok对应的值为由obj_a.is_ok对应的值obj_c。由于变量obj_c中存储的是对一个对象的引用,这里传递的同样是被引用对象的地址,但地址中所存储的呢欧容不会进一步拷贝,这样在new_obj中,并不会存储一个

{are_you_ok : true}

这样的实际对象。因此看起来我们“只拷贝了表层”。这就是所谓“浅”拷贝称为的由来。

这里最后还有一个需要注意的问题,使用Object.assign()方法浅拷贝时由于相当于只是对源对象的所有可枚举的自有键一一进行“=”赋值操作,对于由属性描述符所描述的一些属性的特性是不会被拷贝到目标对象的。如某个属性是否可修改(Writeable)、可配置(Configurable)、可枚举(Enumerable)

3. 深拷贝

在JavaScript中,深拷贝说起来有点麻烦,因为里面情况会很比较复杂。
相比于浅拷贝深拷贝要求要完整地拷贝下底层被引用地对象而不是仅粗略地要求拷贝下引用中对象的地址。正如之前所说通俗一点理解看:

  • 仅用"="将对象引用的赋值给变量时,仅传递了引用的对象地址;
  • 通过“浅拷贝”返回对象时,相当于对源对象最外层的所有可枚举属性进行了“=”操作后获得的新对象;
  • 通过“深拷贝”返回对象时,相当于任意一层不再简单传递引用的对象的内存地址,而是真正意义上拷贝下来每一层对象。

但是很快你就会发现,如果在一个对象中引用的对象到某层由存在循环性的引用,往往会导致一个死循环。
另外,在JavaScript中的函数也是对象,我们在JS中不能确定对一个函数进行“深拷贝”是什么,尽管由很多框架给出了自己的定义。

一种比较好方法是通过JSON序列化来实现深拷贝:

var obj_b = JSON.parse(JSON.stringify(obj_a))

这种方法也不是万能的,它要求对象必须是JSON安全的,即:
不仅obj_a可以被序列化为一个JSON格式的字符串,同时还可以由该字符串解析得到一个结构完全相同的对象。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答1: 使用 JavaScript 写一个深拷贝的方法可以使用如下代码: ``` function deepClone(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } let result; if (Array.isArray(obj)) { result = []; } else { result = {}; } for (const key in obj) { result[key] = deepClone(obj[key]); } return result; } ``` 这是一个递归函数,它判断输入的变量是否是一个对象,如果不是,则直接返回该变量。如果是对象,则判断该对象是数组还是普通对象,并创建一个对应的数组或空对象。最后,遍历该对象的所有属性,并对每个属性进行深拷贝。 ### 回答2: 深拷贝是指在JavaScript复制一个对象或数组时,不仅复制对象的值,还复制对象内部的所有属性和子属性的值。下面是一个用JavaScript编写的深拷贝方法: ```javascript function deepCopy(obj) { // 判断是否为基本数据类型 if (typeof obj !== 'object' || obj === null) { return obj; } // 判断是对象还是数组 const clone = Array.isArray(obj) ? [] : {}; // 递归拷贝对象的每个属性/子属性 for (let key in obj) { clone[key] = deepCopy(obj[key]); } return clone; } ``` 该方法首先判断给定参数是否为基本数据类型,如果是,则直接返回这个值。如果是对象或数组类型,则创建一个空的克隆对象,在循环递归地拷贝给定对象的每个属性和子属性,将其赋值给克隆对象的对应属性。 这个方法会递归地处理对象的所有属性和子属性,确保生成的克隆对象与原对象完全独立,任何的修改都不会相互影响。这就是深拷贝的特性。 使用这个深拷贝方法可以确保在JavaScript正确地复制对象和数组,即使它们包含嵌套的对象和数组。 ### 回答3: 深拷贝是指创建一个完全独立的对象,与原对象具有相同的值,但是在内存是独立存在的,修改其一个对象的值不会影响到另一个对象。下面是一个用JavaScript写的深拷贝方法: ```javascript function deepCopy(obj) { // 如果传入的是基本类型或者是函数,则直接返回 if (typeof obj !== 'object' || obj === null || obj instanceof Function) { return obj; } // 根据传入的对象的类型,创建一个新的对象 const clone = Array.isArray(obj) ? [] : {}; // 遍历原对象的属性,并递归调用深拷贝方法 for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepCopy(obj[key]); } } return clone; } ``` 这个方法首先判断传入的参数是否是基本类型或函数类型,如果是则直接返回,否则进入拷贝过程。在拷贝过程,根据传入对象的类型创建一个新的对象。然后遍历原对象的属性,并通过递归调用深拷贝方法,将原对象的属性以及其值赋值给新对象。最后返回新对象。 通过这个深拷贝方法,我们可以创建一个与原对象具有相同值的独立对象,修改其一个对象的值不会影响到另一个对象

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jcLee95

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值