javascript学习总结之对象的深拷贝和浅拷贝
前言
最近在写ES6文章的时候发现重复遇到关于JavaScript关于对象浅拷贝和深拷贝的问题,然后查找了一些资料,根据资料和自己的理解做了一点笔记,毕竟JavaScript关于对象的浅拷贝和深拷贝的问题在一些面试的时候有些面试官可能会进行提问。
文章的观点仅代表个人,如果表述有误!,还请指出,在下感激不敬
数据类型
在了解浅拷贝和深拷贝之前,我们先回顾一下javascript中的数据类型,因为在讲浅拷贝和深拷贝的时候就是就是对原始数据类型(基本数据类型)和对象数据类型(引用数据类型)的拷贝在javascript中,我们将数据类型分为两种,原始数据类型(基本数据类型)和对象类型(引用数据类型)
基本数据类型
- 基本数据类型的值是按值访问的
- 基本数据类型的值是不可变的
常见的基本数据类型:Number、String、Boolean、Undefined、Null
引用数据类型
- 引用类型的值是按引用访问的
- 引用类型的值是动态可变的
常见的引用类型:Object、Function、Array等
由于数据类型的访问方式不同,它们的比较方式也是不一样的,我们来看下面的示例
基本数据类型和引用数据类型的比较
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>深拷贝和浅拷贝入门</title>
- </head>
- <body>
- <script type="text/javascript">
- var a=100;
- var b=100;
- console.log(a===b);//true
- var c={a:1,b:2};
- var d={a:1,b:2};
- console.log(c===d);//false
- </script>
- </body>
- </html>
总结:
- 基本数据类型的比较是值的比较,所以在示例中a===b为true
- 引用类型的比较是引用地址的比较,所以在示例c===d为false,因为c和d的地址不同
浅拷贝
浅拷贝是对象共用一个内存地址,对象的变化相互影响。
比如:常见的赋值引用就是浅拷贝
简单对象的浅拷贝实现方式一
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>对象的浅拷贝</title>
- </head>
- <body>
- <script type="text/javascript">
- var obj1={name:'cat'};
- var obj2=obj1;
- obj2.name='dog';
- console.log(obj1);//{name:'dog'}
- console.log(obj2);//{name:'dog'}
- </script>
- </body>
- </html>
我们发现当我们改变obj2的值的时候obj1的值也会发生改变,这里到底发生了什么,请看图解
当我们将obj1的值赋值给obj2的时候,仅仅只是将obj1的地址给了obj2而不是obj2在内存中重新开辟一条空间。
所以obj1的地址和obj2的地址指向相同,改变obj2的时候obj1也会发生改变。
使用循环实现浅拷贝
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>使用循环实现浅拷贝</title>
- </head>
- <body>
- <script type="text/javascript">
- var person={
- name:'tt',
- age:18,
- friends:['aa','bb','cc']
- }
- function shallowCopy(source){
- if(!source||typeof source!=='object'){
- throw new Error('error');
- }
- var targetObj=source.constructor===Array?[]:{};
- for(var keys in source){
- if(source.hasOwnProperty(keys)){
- targetObj[keys]=source[keys]
- }
- }
- return targetObj;
- }
- var p1=shallowCopy(person);
- console.log(p1);//{name:'tt',age:18,friends:['aa','bb','cc']}
- </script>
- </body>
- </html>
在上面的代码中,我们创建了shallowCopy函数,它接收一个参数也就是被拷贝的对象,步骤分别是
首先创建了一个对象
然后for...in循环传进去的对象为了避免循环到原型上面会被遍历到的属性,使用
hasOwnProperty
限制循环只在对象自身,将被拷贝对象的每一个属性和值添加到创建的对象当中最后返回这个对象
那么看到这里,我们发现p1拿到了和person一样的对象,那么p1=person又有什么区别了,我们来看下面的示例
简单对象的浅拷贝实现方式二
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>简单对象的浅拷贝</title>
- </head>
- <body>
- <script type="text/javascript">
- var person={
- name:'tt',
- age:18,
- friends:['oo','cc','yy']
- }
- function shallowCopy(source){
- if(!source||typeof source!=='object'){
- throw new Error('error');
- }
- var targetObj=source.constructor===Array?[]:{};
- for(var keys in source){
- if(source.hasOwnProperty(keys)){
- targetObj[keys]=source[keys]
- }
- }
- return targetObj;
- }
- var p1=shallowCopy(person);
- var p2=person;
- //这个时候我们修改person的数据
- person.name='tadpole';
- person.age=19;
- person.friends.push('tt');
- console.log(p2.name);//tadpole
- console.log(p2.age);//19
- console.log(p2.friends);//['oo','cc','yy','tt']
- console.log(p1.name);//tt
- console.log(p1.age);//18
- console.log(p1.friends);//['oo','cc','yy','tt']
- </script>
- </body>
- </html>
上面创建了一个新变量p2,将person的值赋值给p2,然后比较这两个值
和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 | |
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会是原数据一同改变 |
深拷贝
深拷贝是将对象放到一个新的内存中,两个对象的改变不会相互影响或者你可以理解为浅拷贝由于只是复制一层对象的属性,当遇到有子对象的情况时,子对象就会互相影响。所以
深拷贝是对对象以及对象的所有子对象进行拷贝
递归调用实现深拷贝
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>递归实现深拷贝</title>
- </head>
- <body>
- <script type="text/javascript">
- var obj1={
- name:'cat',
- show:function(){
- console.log('名称:'+this.name);
- }
- }
- var obj2=deepClone(obj1);
- obj2.name='pig';
- obj1.show();//cat
- obj2.show();//pig
- function deepClone(obj){
- var objClone=Array.isArray(obj)?[]:{};
- if(obj&&typeof obj==='object'){
- for(key in obj){
- if(obj.hasOwnProperty(key)){
- //判断obj子元素是否为对象,如果是,递归复制
- if(obj[key]&&typeof obj[key]==='object'){
- objClone[key]=deepClone(obj[key])
- }else{
- //如果不是,简单复制
- objClone[key]=obj[key]
- }
- }
- }
- }
- return objClone;
- }
- </script>
- </body>
- </html>
对于深拷贝的对象,改变源对象不会对得到的对象有影响。只是在拷贝的过程中源对象的方法丢失了,这是因为在序列化 JavaScript对象时,所有函数和原型成员会被有意忽略
JSON 对象中的 parse() 和 stringify()实现深拷贝
JSON 对象中的 stringify 可以把一个 JavaScript 对象序列化为一个 JSON字符串,Parse 可以把 JSON字符串反序列化为一个 JavaScript 对象,通过这两个方法,也可以实现对象的深拷贝。
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>利用 JSON 对象中的 parse 和 stringify</title>
- </head>
- <body>
- <script type="text/javascript">
- var obj1={
- name:'cat',
- show:function(){
- console.log(this.name);
- }
- }
- var obj2=JSON.parse(JSON.stringify(obj1));
- obj2.name='dog';
- console.log(obj1.name);//cat
- console.log(obj2.name);//dog
- obj1.show();//cat
- obj2.show();//TypeError: obj2.show is not a function
- </script>
- </body>
- </html>
注意:
JSON.parse()和JSON.stringify()能正确处理的对象只有Number、String、Array等能够被json表示的数据结构,因此函数这种不能被json表示的类型将不能被正确处理,经过转换之后,function丢失了,因此JSON.parse()和JSON.stringify()还是需要谨慎使用。
Object.assgin()方法实现深拷贝
这种方法我在javascript学习总结之Object.assign()方法详解有过讲解,但是查看资料的时候有发现了一点点的问题,所以在这里补充一下
定义:
Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>使用Object.assgin()方法</title>
- </head>
- <body>
- <script type="text/javascript">
- let srcObj = {'name': 'lilei', 'age': '20'};
- let copyObj2 = Object.assign({}, srcObj, {'age': '21'});
- copyObj2.age = '23';
- console.log(srcObj);//{name:'lilei',age:20}
- console.log(copyObj2);//{name:'lilei',age:23}
- </script>
- </body>
- </html>
看起来好像是深拷贝了,那其实这里let copyObj2 = Object.assign({}, srcObj, {'age': '21'});
我们把srcObj 给了一个新的空对象。同样目标对象为 {},我们再来测试下
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>使用Object.assgin()方法</title>
- </head>
- <body>
- <script type="text/javascript">
- let srcObj = {'name': 'lilei', 'age': '20'};
- let copyObj2 = Object.assign({}, srcObj, {'age': '21'});
- copyObj2.age = '23';
- console.log(srcObj);//{name:'lilei',age:20}
- console.log(copyObj2);//{name:'lilei',age:23}
- srcObj = {'name': '明', grade: {'chi': '50', 'eng': '50'} };
- copyObj2 = Object.assign({}, srcObj);
- copyObj2.name = '红';
- copyObj2.grade.chi = '60';
- console.log(srcObj);//{name:'红',grade:{chi:60,eng:50}}
- </script>
- </body>
- </html>
从例子中可以看出,改变复制对象的name 和 grade.chi ,源对象的name没有变化,但是grade.chi却被改变了。因此我们可以看出Object.assign()拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
也就是说,对于Object.assign()而言
如果对象的属性值为简单类型(string, number),通过
Object.assign({},srcObj),
得到的新对象为深拷贝如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的
这是Object.assign()特别值得注意的地方,补充一句,Object.assig({},src1,src2) 对于scr1和src2之间相同的属性是直接覆盖的,如果属性值为对象,是不会对对象之间的属性进行合并的。
文章参考:
https://www.cnblogs.com/best/p/6206268.html
javascript学习总结之对象的深拷贝和浅拷贝的更多相关文章
- JavaScript学习08 Cookie对象
JavaScript学习08 Cookie对象 JavaScript Cookie Cookie对象: Cookie是一种以文件的形式保存在客户端硬盘的Cookies文件夹中的用户数据信息(Cooki ...
- java 复制Map对象(深拷贝与浅拷贝)
java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向 ...
- 谈谈java中对象的深拷贝与浅拷贝
知识点:java中关于Object.clone方法,对象的深拷贝与浅拷贝 引言: 在一些场景中,我们需要获取到一个对象的拷贝,这时候就可以用java中的Object.clone方法进行对象的复制,得到 ...
- JavaScript学习总结 之对象
JavaScript学习总结(二) ---- 对象 在JavaScript中,几乎用到的每个js都离不开它的对象.下面我们深入了解一下js对象. js中对象的分类跟之前我们学过的语言中函数的分类一样, ...
- JavaScript:学习笔记(8)——对象扩展运算符
JavaScript:学习笔记(8)——扩展运算符 对象的扩展运算符 扩展运算符是三个点(...).用于取出参数对象的所有可遍历属性,然后拷贝到当前对象之中. 如上图所示,新建了一个对象a,然后通过扩 ...
- JavaScript学习笔记——BOM_window对象
javascript浏览器对象模型-windwo对象 BOM Browser Object Model window对象 是BOM中所有对象的核心. 一.属性 1.(位置类型-获得浏览器的位置) IE ...
- Javascript学习之Function对象详解
JavaScript中的Function对象,就是我们常说的函数对象.在JS中,所有的函数也是以对象的形式存在的. 语法 充当Function对象的构造函数使用,用于结合new关键字构造一个新的Fun ...
- Javascript学习之Date对象详解
1.定义 创建 Date 实例用来处理日期和时间.Date 对象基于1970年1月1日世界协调时起的毫秒数 2.语法 构造函数 new Date() new Date(value) value代表自世 ...
- JavaScript学习笔记之对象
目录 1.自定义对象 2.Array 3.Boolean 4.Date 5.Math 6.Number 7.String 8.RegExp 9.Function 10.Event 在 JavaScri ...
随机推荐
- Day4 文件管理-常用命令
文件管理 --> 创建 移动 删除 复制 1.cp复制: #####-v:详细显示命令执行的操作 #####-r: 递归处理目录与子目录 #####-p: 保留源文件或目录的属性 #####1. ...
- SpringBoot和Hibernate整合
1.先使用idea创建maven项目(这个就不详细讲了,很简单的操作) 2.创建完maven项目之后添加springboot依赖,pom.xml文件如下: <?xml version=" ...
- elasticsearch集群扩容和容灾
elasticsearch专栏:https://www.cnblogs.com/hello-shf/category/1550315.html 一.集群健康 Elasticsearch 的集群监控信息 ...
- spring项目与logstash和Elasticsearch整合
原创/朱季谦 最近在做一个将项目日志通过logstash传到Elasticsearch的功能模块,经过一番捣鼓,终于把这个过程给走通了,根据自己的经验,做了这篇总结文章,希望可以给各位玩logst ...
- 深入理解Three.js中线条Line,LinLoop,LineSegments
前言 在可视化开发中,无论是2d(canvas)开发还是3d开发,线条的绘制应用都是比较普遍的.比如绘制城市之间的迁徙图,运行轨迹图等.本文主要讲解的是Three.js中三种线条Line,LineLo ...
- MIT FiveK图像转化--DNG到TIFF,TIFF到JPEG
MIT FiveK图像转化--DNG到TIFF,TIFF到JPEG MIT FiveK数据库是研究图像自动修饰算法会用到的基准数据库,然而那个网页上提供给我们的5000张原始图像的格式为DNG格式(一 ...
- MYSQL增删改查添加外键
给商品表(从表)添加一个外键 ALTER TABLE product ADD CONSTRAINT FK_cno FOREIGN KEY(cno) REFERENCES category(cid 从 ...
- hadoop之mapreduce详解(基础篇)
本篇文章主要从mapreduce运行作业的过程,shuffle,以及mapreduce作业失败的容错几个方面进行详解. 一.mapreduce作业运行过程 1.1.mapreduce介绍 MapRed ...
- 基于Babylon.js编写宇宙飞船模拟程序1——程序基础结构、物理引擎使用、三维罗盘
计划做一个宇宙飞船模拟程序,首先做一些技术准备. 可以访问https://ljzc002.github.io/test/Spacetest/HTML/PAGE/spacetestwp2.html查看测 ...
- Docker service endpoint with name xxx already exist问题
这是因为利用docker compose启的容器再用docker rm命令删除后,网络仍然被占用,需要手动清理 解决办法: 先用docker rm -f xxx删除容器 再输入docker netwo ...