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

简介: 本文介绍 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中绝非一切皆是对象,并且“引用”是“对象”的引用。这里就需要区分在赋值操作“=”的右侧,到底是一个“字面量”还是一个对象。举例而言:

vara=1;
varb="Hello World!"varc=a

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

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

而下例中:

vard= {
Hello : 'world'};
vare=d;

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

2. 浅拷贝

浅拷贝,又称“浅层拷贝”、“浅复制”、“浅层复制”等等。

一个很常见的通俗说法,“浅层拷贝就是只拷贝一层”,这样的说法其实不太准确。

浅拷贝的本质特点是:

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

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

varobj_a= {
is_right : true}
varobj_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修改为一个键值中含有对象的对象。如:

varobj_c= {
are_you_ok : true}
varobj_a= {
is_right : true,
is_ok : obj_c}
varobj_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序列化来实现深拷贝:

varobj_b=JSON.parse(JSON.stringify(obj_a))

这种方法也不是万能的,它要求对象必须是JSON安全的,即:

不仅obj_a可以被序列化为一个JSON格式的字符串,同时还可以由该字符串解析得到一个结构完全相同的对象。

目录
相关文章
|
1月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
1月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
1月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
19 1
JavaScript中对象的数据拷贝
|
1月前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
|
2月前
|
存储 JavaScript 前端开发
JavaScript 对象的概念
JavaScript 对象的概念
49 4
|
2月前
|
缓存 JavaScript 前端开发
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
50 1
|
2月前
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
23 2
|
2月前
|
JavaScript 前端开发 Unix
Node.js 全局对象
10月更文挑战第5天
39 2
|
2月前
|
JavaScript 前端开发 大数据
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
38 0
|
2月前
|
JavaScript 前端开发 索引
JavaScript中数组、对象等循环遍历的常用方法介绍(一)
JavaScript中数组、对象等循环遍历的常用方法介绍(一)
40 0