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 这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副 ...
随机推荐
- 动态规划的状态设计 | bot 讲课の补题
sto james1badcreeper orz. 好厉害的题,但是怎么有人补了三天才补完呢? CF1810G The Maximum Prefix 线性 dp,怎么有 bot 说题目难度在 *240 ...
- [C++]P3384 轻重链剖分(树链剖分)
[C++]树链剖分 预备知识 树的基础知识 关于这个本文有介绍 邻接表存图 线段树基础 会区间加法和区间结合就可以了P3372 建议阅读这篇Blog 最近公共祖先LCA 虽然用不到这个思想 但是有类似 ...
- mysql常用函数详解
1. Mysql内置函数分类及使用范围 数学函数: 这类函数只要用于处理数字.这类函数包括绝对值函数.正弦函数.余弦函数.获取随机数函数等. 字符串函数:这类函数主要用于处理字符串.其中包括字符串连接 ...
- 使用GPT4进行数据分析,竟然被他骗了
上周,OpenAI开发者大会上OpenAI发布了一系列震撼人心的功能.而最让我感兴趣的,就是GPT4的数据分析功能了.话不多说,赶紧上号体验一下. 在最新登录GPT4的时候,都会有下面这个提示,目前已 ...
- 最佳实践-使用Github Actions来构建跨平台容器镜像
公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 前言 最近在写K8s的相关系列文章,因为有涉及到镜像构建,发现在Mac m1的Arm架构下构建的部分镜像,没法在X86架构 ...
- Ubuntu 20.04 查看显示器信息
安装 ddcutil apt install ddcutil 输入命令 ddcutil detect --verbose 输出类似如下: Output level: Verbose Reporting ...
- 在Vue3中使用Element-Plus分页(Pagination )组件
在Vue3中使用Element-Plus分页(Pagination )组件 开发过程中数据展示会经常使用到,同时分页功能也会添加到页面中. 记:在Vue3中使用Element-Plus分页组件与表格数 ...
- Java Stream中的API你都用过了吗?
公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 在本教程中,您将通过大量示例来学习 Java 8 Stream API. Java 在 Java 8 中提供了一个新的附加 ...
- Keepalived+Nginx高可用案例(抢占式与非抢占式)
(1)下载安装Keepalived源码包 Keepalived官网源码包下载地址 在服务器上解压 tar -xf keepalived-2.2.8.tar.gz 安装相关前置依赖 yum -y ins ...
- GPTs 初体验 - 1 分钟就能创建一个自己的 ChatGPT?
就在 11.10 号早上,ChatGPT 已经偷摸的把GPTs功能,开放给所有尊贵的 Plus 用户了. 随着这波的功能开放,界面也是改了不少.点击左侧的 Explore 或者左下角的用户处,就可以直 ...