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 这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副 ...
随机推荐
- 程序是如何在计算机上被执行的?(下篇:cpu工作原理)
本文接上文程序是如何在计算机上被执行的?(上篇:软件部分),主要内容是机器语言如何在计算机硬件上运行,关于逻辑门,加法器,布尔运算,亦即,cpu的工作原理. 1.逻辑门 以下图片是<三体> ...
- Visual Studio vs2010到2022各个版本的的永久激活密钥
前言 以下密钥均收集于网络,但均可以正常激活 VS2022专业版和企业版的密钥 Visual Studio 2022 Pro(专业版) TD244-P4NB7-YQ6XK-Y8MMM-YWV2J Vi ...
- JVM-Java虚拟机是怎么实现synchronized的?
1. JVM的锁优化 今天我介绍了 Java 虚拟机中 synchronized 关键字的实现,按照代价由高至低可分为重量级锁.轻量级锁和偏向锁三种. 重量级锁会阻塞.唤醒请求加锁的线程.它针对的是多 ...
- [Python急救站课程]天天向上的力量
我们要"好好学习,天天向上."那么天天向上的力量到底有多强呢? 1.一年365天,以第1天的能力值为基数,记为1.0,当好好学习时能力值相比前一天提高1‰,当没有学习时由于遗忘等原 ...
- 浅谈斜率优化DP
前言 考试 T2 出题人放了个树上斜率优化 DP,直接被同校 OIER 吊起来锤. 离 NOIP 还有不到一周,赶紧学一点. 引入 斜率 斜率,数学.几何学名词,是表示一条直线(或曲线的切线)关于(横 ...
- 【Flutter】一文读懂混入类Mixin
[Flutter]一文读懂混入类Mixin 基本介绍 Mixin是一种有利于代码复用,又避免了多继承的解决方案. Mixin 是面向对象程序设计语言中的类,提供了方法的实现,其他类可以访问 Mixin ...
- 通过计算巢轻松部署 Ansible Semaphore
概述 Ansible Semaphore 是一个现代化的 Ansible 用户界面.可以轻松运行 Ansible Playbook,获取有关失败的通知,并控制部署系统的访问权限.如果你的项目已经发展壮 ...
- Netty源码学习5——服务端是如何读取数据的
系列文章目录和关于我 零丶引入 在前面<Netty源码学习4--服务端是处理新连接的&netty的reactor模式>的学习中,我们了解到服务端是如何处理新连接的,即注册Serve ...
- java-图片添加水印
前言: 需求:需要在图片中添加水印,防止盗用 优缺点: 优点:保护版权,防止盗用 缺点 可能会影响图片的视觉效果:如果水印过大或过醒目,可能会影响图片的视觉效果. 可能会增加 ...
- 搭建一个简易的IPv6网络测试环境
背景 近期一个项目要求产品在IPv6网络环境部署,在此之前所有的项目网络环境都是IPv4,为了验证产品网络适配能力,需要搭建一套IPv6的网络测试环境,网上搜了很多教程,也在某东找了很多路由器,对于如 ...