JS对象拷贝:深拷贝和浅拷贝
摘要:对象拷贝,简而言之就是将对象再复制一份,但是,复制的方法不同将会得到不同的结果。
本文分享自华为云社区《js对象深浅拷贝,来,试试看!》,作者: 北极光之夜。。
一.速识概念:
对象拷贝,简而言之就是将对象再复制一份,但是,复制的方法不同将会得到不同的结果。比如直接给新变量赋值为一个对象:
// 1.建一个对象
var obj = {
name: "北极光之夜。",
like: "aurora",
};
// 2. 直接将对象赋值给变量 clone
var clone = obj;
// 3.修改obj的like属性
obj.like = "wind";
// 4.输出 clone 对象
console.log(clone);
从输出结果可以看到,我明明改变的是 obj 对象的属性,但是 clone 对象的属性也改变了。这是因为,当创建 obj 对象时,它在堆内存中开辟了一块空间存储对象的内容。而当 clone 直接赋值为 obj 时,clone 并不会再重新开辟一块堆内存,而是 obj 跟 clone 说我把我这内存空间存储的对象的地址给你,这个地址存在栈内存中,你通过栈内存的地址找到堆内存里对象的内容,咱们共用就完事了。所以说, obj 和 clone 指向的都是同一块内容,不管谁改了对象的内容,别人再访问都是改过之后的了。

所以这不是我们想要的,我不想共用,我想要属于自己的一片天地,我命由我不由你,所以这就需要浅拷贝和深拷贝了。
简单补充: 像一些基本数据类型的变量(Number Boolean String undefined null)被赋值时会直接在栈内存中开辟出了一个新的存储区域用来存储新的变量,不会如对象那样只是把引用给别人。
二.浅拷贝原理与常用方法:
简单来说浅拷贝就是只拷贝一层。什么意思呢 ?比如我有一个对象 obj :
var obj = {
name: "北极光之夜。",
like: "aurora",
};
我要把它拷贝
给变量 b ,原理就是我再重新开辟一块内存,然后我直接看 obj 里有什么属性和值就直接复制一份,比如通过如下方式实现:
// 1.建一个对象
var obj = {
name: "北极光之夜。",
like: "aurora",
};
// 2. 封装一个函数,实现传入一个对象返回一个拷贝后的新对象
function cloneObj(obj) {
let clone = {};
// 3.用 for in 遍历obj的属性
for (let i in obj) {
clone[i] = obj[i];
}
return clone;
}
// 4.执行函数,将得到一个新对象
var clone = cloneObj(obj);
// 5.更改 obj 属性值
obj.like = "wind";
// 6.输出
console.log(clone);
结果:

可以看到,就是新建一个空对象,还是循环直接赋值给它,这时改变 obj 的like属性值 ,新建的那个对象也不受影响了。但是,如果 obj 是下面这种形式的呢:
var obj = {
name: "北极光之夜。",
like: "aurora",
num: {
a: "1",
b: "2",
},
};
此时再用上面那种方法就不行了,如果obj只改变像 name 这种属性还没问题,但是当 obj 改变得是像 num 这种引用类型(对象、数组都是引用类型)的数据时,拷贝的对象还是能被影响,因为浅拷贝只能拷贝一层,如果拷贝的对象里还有子对象的话,那子对象拷贝其是也只是得到一个地址指向而已。这通过上面代码也能看出,就一层循环而已。想要真的达到我命由我不由天的话得用深拷贝,真正的刨根问底。深拷贝见第三大点。下面介绍下浅拷贝常用的方法,当对象只有一层的时候还是用浅拷贝好。
浅拷贝常用的方法:
1.第一种是主要利用 for in 遍历原对象的属性。
// 封装一个函数,实现传入一个对象返回一个拷贝后的新对象
function cloneObj(obj) {
let clone = {};
// 用 for in 遍历obj的属性
for (let i in obj) {
clone[i] = obj[i];
}
return clone;
}
2.可以用Object.keys()方法:
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组。
function cloneObj(obj) {
let clone = {};
for (let i of Object.keys(obj)) {
clone[i] = obj[i];
}
return clone;
}
3.可以用Object.entries()方法:
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组。
function cloneObj(obj) {
let clone = {};
for (let [key, value] of Object.entries(obj)) {
clone[key] = value;
}
return clone;
}
4.可用Object.getOwnPropertyNames()配合forEach循环:
Object.getOwnPropertyNames()返回一个由它的属性构成的数组。
function cloneObj(obj) {
let clone = {};
Object.getOwnPropertyNames(obj).forEach(function (item) {
clone[item] = obj[item];
});
return clone;
}
5.可用Object.defineProperty()方法:
Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。obj要定义属性的对象。prop要定义或修改的属性的名称或 Symbol。descriptor要定义或修改的属性描述符。
Object.getOwnPropertyDescriptor():返回指定对象上一个自有属性对应的属性描述符。
属性描述符:JS 提供了一个内部数据结构,用来描述对象的值、控制其行为。称为属性描述符。
function cloneObj(obj) {
let clone = {};
Object.getOwnPropertyNames(obj).forEach(function (item) {
// 获取原本obj每个属性修饰符
var des = Object.getOwnPropertyDescriptor(obj, item);
// 把属性修饰符赋值给新对象
Object.defineProperty(clone, item, des);
});
return clone;
}
还有很多方法,就不一一列举了
三.深拷贝常见方法:
深拷贝就不会像浅拷贝那样只拷贝一层,而是有多少层我就拷贝多少层,要真正的做到全部内容都放在自己新开辟的内存里。可以利用递归思想实现深拷贝。
1.可以如下实现,还是用 for in 循环,如果为属性对象则递归:
function cloneObj(obj) {
let clone = {};
for (let i in obj) {
// 如果为对象则递归更进一层去拷贝
if (typeof obj[i] == "object" && obj[i] != null) {
clone[i] = cloneObj(obj[i]);
} else {
clone[i] = obj[i];
}
}
return clone;
}
试一试看:
// 1.建一个对象
var obj = {
name: "北极光之夜。",
like: "aurora",
age: {
a: 1,
b: 2,
},
}; // 2. 封装一个函数,实现传入一个对象返回一个拷贝后的新对象
function cloneObj(obj) {
let clone = {};
for (let i in obj) {
// 如果为对象则递归更进一层去拷贝
if (typeof obj[i] == "object" && obj[i] != null) {
clone[i] = cloneObj(obj[i]);
} else {
clone[i] = obj[i];
}
}
return clone;
}
// 4.执行函数,将得到一个新对象
var clone = cloneObj(obj);
// 5.更改 obj 属性值
obj.age.a = "666";
// 6.输出
console.log(clone);
结果如下,拷贝成功,原对象改变无法使新对象也改变:

2.如果对象里面有数组怎么办,数组也跟对象一样是引用类型,那么我们可以在开头加个判断它是对象还是数组,数组的话赋空数组,一样遍历拷贝:
function cloneObj(obj) {
// 通过原型链判断 obj 是否为数组
if (obj instanceof Array) {
var clone = [];
} else {
var clone = {};
}
for (let i in obj) {
// 如果为对象则递归更进一层去拷贝
if (typeof obj[i] == "object" && obj[i] != null) {
clone[i] = cloneObj(obj[i]);
} else {
clone[i] = obj[i];
}
}
return clone;
}
试一试看:
var obj = {
name: "北极光之夜。",
like: "aurora",
age: {
a: [1, 2, 3],
b: 2,
},
};
// 2. 封装一个函数,实现传入一个对象返回一个拷贝后的新对象
function cloneObj(obj) {
// 先判断 obj 是否为数组
if (obj instanceof Array) {
var clone = [];
} else {
var clone = {};
}
for (let i in obj) {
// 如果为对象则递归更进一层去拷贝
if (typeof obj[i] == "object" && obj[i] != null) {
clone[i] = cloneObj(obj[i]);
} else {
clone[i] = obj[i];
}
}
return clone;
}
// 4.执行函数,将得到一个新对象
var clone = cloneObj(obj);
// 5.更改 obj 属性值
obj.age.a[1] = "666";
// 6.输出
console.log(clone);
结果没问题:

当然,也可用Array.isArray(obj)方法用于判断一个对象是否为数组。如果对象是数组返回 true,否则返回 false。
function cloneObj(obj) {
// 判断 obj 是否为数组
if (Array.isArray(obj)) {
var clone = [];
} else {
var clone = {};
}
for (let i in obj) {
// 如果为对象则递归更进一层去拷贝
if (typeof obj[i] == "object" && obj[i] != null) {
clone[i] = cloneObj(obj[i]);
} else {
clone[i] = obj[i];
}
}
return clone;
}
四.总结:
以上就是深浅拷贝的大致内容啦。因为对象是引用类型,所以直接赋值对象给新变量,那么新变量指向的内存和原对象是一样的。所以我们通过浅拷贝和深拷贝实现开辟自己的内存空间。而浅拷贝只拷贝一层,深拷贝拷贝全部。如果,文章有什么错误的,恳请大佬指出。
JS对象拷贝:深拷贝和浅拷贝的更多相关文章
- JS对象复制(深拷贝、浅拷贝)
如何在 JS 中复制对象 在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法. 在开始之前,有一些基础知识值得一提:Javas ...
- JS 对象的深拷贝和浅拷贝
转载于原文:https://www.cnblogs.com/dabingqi/p/8502932.html 这篇文章是转载于上面的链接地址,觉得写的非常好,所以收藏了,感谢原创作者的分享. 浅拷贝和深 ...
- Python对象拷贝——深拷贝与浅拷贝
对象赋值 浅拷贝 深拷贝 1. 对象赋值 对象的赋值实际上是对对象的引用.也就是说当把一个对象赋值给另一个对象时,只是拷贝了引用.如: >>> t1 = tuple('furzoom ...
- java 复制Map对象(深拷贝与浅拷贝)
java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向 ...
- 谈谈java中对象的深拷贝与浅拷贝
知识点:java中关于Object.clone方法,对象的深拷贝与浅拷贝 引言: 在一些场景中,我们需要获取到一个对象的拷贝,这时候就可以用java中的Object.clone方法进行对象的复制,得到 ...
- js中的深拷贝与浅拷贝
对象的深拷贝于浅拷贝 对于基本类型,浅拷贝过程就是对值的复制,这个过程会开辟出一个新的内存空间,将值复制到新的内存空间.而对于引用类型来书,浅拷贝过程就是对指针的复制,这个过程并没有开辟新的堆内存空间 ...
- js 中数组或者对象的深拷贝和浅拷贝
浅拷贝 : 就是两个js 对象指向同一块内存地址,所以当obj1 ,obj2指向obj3的时候,一旦其中一个改变,其他的便会改变! 深拷贝:就是重新复制一块内存,这样就不会互相影响. 有些时候我们定义 ...
- 探究JS中对象的深拷贝和浅拷贝
深拷贝和浅拷贝的区别 在讲深拷贝和浅拷贝的区别之前,回想一下我们平时拷贝一个对象时是怎么操作的?是不是像这样? var testObj1 = {a: 1, b:2}, testObj2=testObj ...
- 一篇文章理解JS数据类型、深拷贝和浅拷贝
前言 笔者最近整理了一些前端技术文章,如果有兴趣可以参考这里:muwoo blogs.接下来我们进入正片: js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. ...
- js 中的深拷贝与浅拷贝
在面试中经常会问到js的深拷贝和浅拷贝,也常常让我们手写,下面我们彻底搞懂js的深拷贝与浅拷贝. 在js中 Array 和 Object 这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副 ...
随机推荐
- Golang 面向对象深入理解
1 封装 Java 中封装是基于类(Class),Golang 中封装是基于结构体(struct) Golang 的开发中经常直接将成员变量设置为大写使用,当然这样使用并不符合面向对象封装的思想. G ...
- Java代码审计之目录穿越(任意文件下载/读取)
一.目录穿越漏洞 1.什么是目录穿越 所谓的目录穿越指利用操作系统中的文件系统对目录的表示.在文件系统路径中,".."表示上一级目录,当你使用"../"时,你正 ...
- 【发布】DDD 工程脚手架 + 一键安装分布式技术栈环境!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 大家好,我是技术UP主小傅哥. 写了那么多案例工程,开发了那么多技术项目.那小傅哥做的这些案例 ...
- SQL INSERT INTO 语句详解:插入新记录、多行插入和自增字段
SQL INSERT INTO 语句用于在表中插入新记录. INSERT INTO 语法 可以以两种方式编写INSERT INTO语句: 指定要插入的列名和值: INSERT INTO 表名 (列1, ...
- 抽象工厂(Abstract Factory)
模式定义 提供一个接口, 让该接口负责创建一系列'相关或者相互依赖的对象' , 无需指定他们具体的类 要点总结 如果没有应对'多系列对象构建'的需求变化 ,则没有必要使用Abstract Factor ...
- Opencv学习笔记(1)
1.安装环境 如何安装Python解释器和PyCharm,这里就不说明了, 参考文章链接:https://blog.csdn.net/weixin_72959097/article/details/1 ...
- 火山引擎DataTester升级MAB功能,助力企业营销决策
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 DataTester,火山引擎推出的 AB 测试与智能优化平台,近日宣布对其 MAB(Multi-armed Ba ...
- VO、DTO、Entity的区别
只能说从实际用法的一般习惯上进行区分: 1.entity 里的每一个字段,与数据库相对应,注意:entity与对应的实际数据库表的字段 没有强制要求但是一般建议保持一致(包括字段数据类型),当然,从实 ...
- Pytorch实现YOLOv3训练自己的数据集
1.说明: 最近一直在研究深度学习框架PyTorch,就想使用pytorch去实现YOLOv3的object detection.在这个过程中也在各大论坛.贴吧.CSDN等中看了前辈们写的文章,在这里 ...
- 解决OpenCV3+VS2015(VS2017)运行时出现debug error abort()has been called的问题
问题描述: 在windows平台上安装opencv后,测试一张图片时,出现了debug error abort()has been called的问题 环境: vs2015 windows 10 op ...