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 这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副 ...
随机推荐
- Go 如何实现多态
在 Go 语言中,虽然没有经典的面向对象编程中的继承和多态的概念,但你可以通过接口(interface)来实现多态性.Go 语言鼓励组合和接口多态,这使得代码更加灵活和模块化.下面将详细介绍 Go 语 ...
- 04Java学习_DOS原理和路径详解
DOS原理和路径详解 目录 DOS原理和路径详解 DOS原理 路径详解 DOS常用命令 DOS原理 Dos:Disk Operating System 磁盘操作系统. 路径详解 相对路径:从当前目录开 ...
- 01Java学习_概述
概述 目录 概述 三个阶段 概述 历史(了解) Java 重要特点 开发工具 Java 运行机制及运行过程 下载和安装 JDK 配置环境变量 java 快速入门 博客中的图片内容全部采用 github ...
- 在 Ubuntu 22.04 系统上为 SSH 开启基于时间的 TOTP 认证
前言 一次性密码(英语:one-time password,简称OTP),又称动态密码或单次有效密码,是指电脑系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或一段短时间内.基于时间的 ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-34-处理https 安全问题或者非信任站点-下篇
1.简介 这一篇宏哥主要介绍playwright如何在IE.Chrome和Firefox三个浏览器上处理不信任证书的情况,我们知道,有些网站打开是弹窗,SSL证书不可信任,但是你可以点击高级选项,继续 ...
- 华为ar502H物联网边缘计算网关,在容器内控制/dev/do0开关命令
执行以下命令进行开关do继电开关,可以听见电位器声音. echo -en "\x01" > /dev/do0 echo -en "\x00" > ...
- Codeforces Round 878 (Div. 3)
Codeforces Round 878 (Div. 3) A:ABC A. Cipher Shifer 题意:在自身后面添加一个字母,但是不能添加自身 思路:找到第二个与自身相符的就再找 #incl ...
- raft算法的自我理解
1.raft算法是什么? 答:共识算法 2.raft算法有什么用? 答:维持不同机器的强一致性 3.raft算法通过什么方式来维持不同机器的强一致性? 答:传递log日志 ,按照官方的说法日志里面包含 ...
- c++学习,和友元函数
第一友元函数访问私有元素时不会显示,但是是可以调用的(我使用的是gcc10.3版本的)友元函数可以访问任何元素.就是语法你别写错了. 继承如果父类已经写了构造函数,子类一定要赋值给构造函数,要么父类就 ...
- 倒计时4天!解锁《2023 .NET Conf China》 云原生分会场精彩议程
.NET Conf China 2023 定于 12 月16 日于北京举办为期一天的技术交流,届时会有.NET 领域专家与大家一同庆祝 .NET 8 的发布和回顾过去一年来 .NET 在中国的发展成果 ...