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 这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副 ...
随机推荐
- Unity csc.rsp文件
试验版本 Unity2020.3 编译符号,就是#if UNITY_ANDROID这个东西,在处理多平台多分支的情况下挺有用的,但是在ProjectSettings里面设置比较麻烦,不主动代码调用保存 ...
- C# -WebAPIOperator.cs
说明:一个用C#编写的WebAPI操作类,只写了Get Post 部分. using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System ...
- 极致性能优化:前端SSR渲染利器Qwik.js
引言 前端性能已成为网站和应用成功的关键要素之一.用户期望快速加载的页面和流畅的交互,而前端框架的选择对于实现这些目标至关重要.然而,传统的前端框架在某些情况下可能面临性能挑战且存在技术壁垒. 在这个 ...
- pbootcms 后台内容列表搜索功能扩展及增加显示字段功能
应项目要求,一个内容模型下栏目不宜分的层级过多,如新闻模块,分2022.2023.2024年度,每年度下分12个月,这样就是2层栏目,再依类别(科技.动漫.电影...)划分层级,栏目数量较多,而且不易 ...
- 微服务系列-基于Spring Cloud Eureka进行服务的注册与消费
公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 在之前的几个教程中,我们学了: 使用 RestTemplate 的 Spring Boot 微服务通信示例 使用 WebC ...
- 等保测评之主机测评——Windows Sever
目录 (一)身份鉴别 (二)访问控制 (三)安全审计 (四)入侵防范 (五)恶意代码防范 (六)可信验证 (七)数据完整性 (八)数据保密性 (九)数据备份恢复 (十)剩余信息保护 在测评过程中最为常 ...
- JSX、TSX 整体理解
可以少去理解一些不必要的概念,而多去思考为什么会有这样的东西,它解决了什么问题,或者它的运行机制是什么? JS JavaScript 是互联网上最流行的脚本语言,这门语言可用于 HTML 和 web, ...
- Leetcode回文数
直接上python代码 class Solution: def isPalindrome(self, x: int) -> bool: if x<0: //负数必不是回文数 return ...
- LeetCode-Java:122. 买卖股票的最佳时机Ⅱ
题目 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格. 在每一天,你可以决定是否购买和/或出售股票.你在任何时候 最多 只能持有 一股 股票.你也可以先购买, ...
- SpringBoot项目中常见组件的配置属性
本文本的属性摘录自官方Properties配置清单,并附加了国内开发常用的框架配置属性.以国内WEB开发中,所涉及的常见组件为顺序组织配置清单 1. 配置属性清单 1.1 日志配置 序号 属性名 类型 ...