JavaScript浅拷贝与深拷贝以及对象与JSON格式的转换JSON.stringifyJSON.parse
Posted 冯大少
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript浅拷贝与深拷贝以及对象与JSON格式的转换JSON.stringifyJSON.parse相关的知识,希望对你有一定的参考价值。
浅拷贝 : 对于引用类型而言,指两个引用类型指向同一个地址,改变一个,另一个也会随之改变。
例如当设置 ku 的变量值 为 age : 18 , angel 的变量值指向 ku, 在栈内存里,angel 的内存地址和 ku 是一样的,在堆内存里,它们是同一个 object 对象。
当改变 ku age 的属性值时,angel age的属性值也随之改变。
深拷贝: 对于引用类型而言,复制后引用类型指向一个新的内存地址,两个对象改变互不影响。
首先简单介绍 JSON(javascript Object Notation)是JavaScript表达值和对象的通用数据格式,其本质就是符合一定规范的字符串。由于JSON的优良特性,非常容易和其他语言进行数据交换,尤其在前后端交互方面。
在之前的博客也分享了一个关于 JS 反序列化的爬虫项目案例,这里先通过以下序列化和反序列化的例子,说明深拷贝与浅拷贝的区别,接着会详细举例说明序列化和反序列化的各种用法。
通过 JSON.stringify (序列化) 转化为 JSON 字符串格式
再通过 JSON.parse (反序列化) ,赋值给 angel, 当 ku age 的改变时, angel age 的值不变。 也就是 在栈内存和堆内存里,ku 和 angel 各自有独立的 内存地址和 object 对象,当发生改变时,两者互不影响。
== 注意:基础类型的赋值和深拷贝相似,但不属于深拷贝 。==
使用JSON.stringify(obj)方法会返回该对象obj的JSON字符串数据,但归属于JavaScript语言本身的非数据属性会被JSON.stringify跳过,这包括对象方法、Symbol类型、undefined的属性。
可见,输出为空的内容
并非所有的对象都能转为JSON格式,如果对象之间存在循环引用,就会导致转换失败。失败的原因是由于 teacher 引用了 student, student 反过来又引用了 teacher。
如果想只将对象的个别属性转为JSON格式,或者摆出循环应用中的属性,可以 通过 let json = JSON.stringify(obj[,replacer,space]),其中参数 obj : 要编码的对象;replacer:要编码的属性数组或者映射函数function(k,v);space:用于格式化的空格数量。
如果想要在第二个参数传入一个数组,那么JSON.stringify就会只把数组中的名称转为JSON格式,这样计算对象存在循环引用,同样能够成功的转格式。
如果我们希望序列化出循环应用外的所有对象属性,只需要把对象的所有属性名写入数组即可,这对对象的子对象同样生效。
执行结果如下,但还存在一个问题,如果对象属性特别多,可能数组就会非常长,代码也会很冗长,这种情况下就需要使用映射函数。
映射函数: 我们可以创建一个函数,代替数组作为replacer,这个函数接收(key,value)作为参数,并决定如何序列化对应的属性。
例如,在解决循环引用的时候,我们排除引用属性。由于值为undefined的属性会被JSON.stringify忽略,这样我们就可以轻松的排除所有不希望出现的属性了。
格式化使用的空格数量: JSON.stringify(value, replacer, spaces)的第三个参数spaces可以指定JSON字符串的缩进空格数,常用的数值有2、4两种。在以上例子中,没有指定缩进空格数量,所以格式化后的JSON字符串都是没有格式的。
这样输出的缩进更加清晰
自定义toJSON方法: 和toString一样,对象的toJSON方法会在序列化的时候调用,我们可以通过重写这个方法改变序列化的方式。
可以看到,在重写了对象的toJSON方法后,使用stringify的结果发生了改变。我们可以根据自己的需要重写toJSON方法,从而达到要的结果。
JSON.parse: 之前简单介绍了反序列化的例子,现在就详细介绍如何把JSON字符串转为对象。语法:let obj = JSON.parse(str,[reviver]) 。 str 要解析的 JSON 字符串; reviver 可选的函数 function(key,value),该函数将为每个 (key, value) 对调用,并可以对值进行转换。
使用reviver, 例如当我们要将字符串’“title”:“Conference”,“date”:“2017-11-30T12:00:00.000Z”’ 转为对象,结果会遇到报错。原因是date属性被转为了字符串,而不是Date对象。
这就需要使用reviver函数将date转为Date对象:
receiver 也可用于嵌套对象
JavaScript数组对象的浅拷贝与深拷贝深浅拷贝的区别+图解原理
JavaScript数组对象的浅拷贝和深拷贝
知识回调(不懂就看这儿!)
知识专栏 | 专栏链接 |
---|---|
JavaScript知识专栏 | https://blog.csdn.net/xsl_hr/category_12024214.html?spm=1001.2014.3001.5482 |
有关JavaScript的相关知识可以前往JavaScript知识专栏查看复习!!
场景复现
在程序员的日常开发中,一定离不开数据的分析与处理,无论是使用什么语言进行编程,都会涉及到相应的数据处理。而其中比较常见也很经典的问题便是数据的深浅拷贝。
首先解释一下深浅拷贝出错会导致什么样的结果
- 你发现上一行代码打印出来的值是正确的,但是下一行就不对了,
- 或者单个的数据打印是正确的,一旦你打印整体的数据,就会不符合要求。
- 还有很多例子,都是数据的深浅拷贝出错所导致的。
下面我们来从底层原理来学习什么是浅拷贝,什么是深拷贝。👇👇👇
底层知识与原理
1.关于内存(图解)
名词 | 解释 |
---|---|
栈内存(stack) | 会自动分配的内存空间,它由系统自动释放 |
堆内存(heap) | 动态分配的内存及大小,不一定会自动释放 |
2.关于数据类型
名词 | 解释 |
---|---|
基本数据类型 | String , Number , Boolean , undefined , null , Symbol |
引用数据类型 | Object , Array , Function |
在JS中,数据类型分为基本数据类型和引用数据类型两种
- 对于基本数据类型来说,它的直接存储在栈内存
- 而对于引用类型来说,在栈内存中仅仅只存储了一个引用,而真正的数据存储在堆内存中
3.关于赋值的原理
赋值是将某一数值或对象赋给某个变量的过程
基本数据类型的赋值
代码示例:
var a = 1;
var b = a;
console.log(b) // 1
a = 2;
console.log(b) // 1
b = 3;
console.log(a) // 2
- 基本数据类型:我们可以发现,对于基本的数据而言,当我们进行赋值时,系统会自动对变量b在栈内存中开辟一块新的内存进行存储,变量a与变量b的值互不影响,相互独立。
- 我们可以画个图来辅助理解:
引用数据类型的赋值:
代码示例:
let a2 = a:1, b:'Trist' ;
let b2 = a2;
console.log(b2) // a: 1, b: "Trist"
b2.a = 2;
console.log(a2.a) // 2
- 引用数据类型: 通过对于引用数据类型的实验,我们发现与基本数据类型不同的是,两个变量之间会相互干扰。为什么会出现这种现象呢?其实是因为引用数据类型存储在堆内存中,当进行赋值时,系统为变量b2传入了变量a2指向堆内存中地址的指针,实际上他们俩指向的是同一个对象,所以当第二个变量的值改变的时候,第一个变量的值也会改变。
- 同样的我们可以画个图辅助理解:
注意:深浅拷贝只针对于引用类型。因为只有引用数据类型才会出现变量相互干扰和内存地址的问题。
深拷贝与浅拷贝
浅拷贝实现原理(图解)
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。(只复制对象空间而不复制资源)
- 但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
实现原理:创建一个拷贝的方法 clone(),通过遍历传入对象的键名及键值,然后赋值给一个空对象,这样就完成了对原对象的浅拷贝。
代码示例:
function clone(obj)
var cloneObj = ;
for(var key of Object.keys(obj))
cloneObj[key] = obj[key];
return cloneObj;
var a3 =
a: 1,
b: 'Trist',
c: d: 1
var b3 = clone(a3)
console.log(b3) // a: 1,b: "Trist",c: d: 1
a3.a = 2;
console.log(b3.a) // 1
b3.b = '张三',
console.log(a3.b) // Trist
b3.c.d = 2;
console.log(a3.c.d) // 2
我们可以发现,当我们拿到a3的浅拷贝后,对于对象中的基本数据类型而言,相互之间是互不影响的,而对于引用数据而言,相互之间会互相干扰。
对于浅拷贝的理解:
- 浅拷贝只复制了原对象最外层的属性,也就是拷贝了基本数据类型的数据,而对于引用数据类型而言,它仅仅是复制了其引用,指向的地址还是原对象的地址。
- 因此我们在开发中碰到的赋值出现问题,原因可能就是使用了浅拷贝,并没有将新的数据存入新的地址,达到深层拷贝的效果。
图解:
注意:
通过对浅拷贝的认识,如果我们要实现对深层级的数据进行的操作不仅仅是单纯的引用,则需要对原对象内所有的属性值进行递归遍历,这就是相对于浅拷贝而言更深层的深拷贝了。
深拷贝实现原理(图解)
实现原理:首先进行遍历,判断原对象内的属性是否还有对象以及空值null,若有则判断对象是否为数组,有则赋值空数组,无则赋值空对象,然后进行递归。
代码示例:
function deepClone(obj, cloneObj)
var cloneObj = cloneObj || ;
for(var i in obj)
// 通过遍历判断属性是否为引用类型,此处注意null因为历史遗留bug通过typeof输出为object
if(typeof obj[i] === 'object' && typeof obj[i] !== null)
// 判断引用值是否为数据 obj[i] instanceof Array
cloneObj[i] = (obj[i].constructor === Array) ? [] : ;
// 进行递归
deepClone(obj[i], cloneObj[i]);
else
cloneObj[i] = obj[i];
return cloneObj;
var b4 = deepClone(a4,b4);
console.log(b4)
a4.a = 2;
console.log(b4.a) // 1
b4.b = '张三',
console.log(a4.b) // Trist
console.log(a4.c) // 1
console.log(b4.c) // 1
b4.c.d = 2;
console.log(a4.c) // 1
console.log(b4.c) // 2
console.log(a4.size) // [1, 2, 3]
console.log(b5.size) // [1, 2, 3]
b5.size.push(4,5,6)
console.log(a4.size) // [1, 2, 3]
console.log(b5.size) // [1, 2, 3, 4, 5, 6]
我们可以发现,不管是原对象内的基本数据类型,还是对象、数组,我们在进行属性的修改添加时,发现他们都各自独立并且互不影响。
对于深拷贝的理解:
- 深拷贝不会拷贝引用类型的引用,而是将引用类型的值全部拷贝一份,形成一个新的引用类型,这样就不会发生引用错乱的问题,使得我们可以多次使用同样的数据,而不用担心数据之间会起冲突。
图解:
以上就是关于JavaScript深拷贝和浅拷贝的底层原理分析与介绍,配有详细的代码示例和图文讲解,相信看完这篇博客的小伙伴们已经对深浅拷贝有了一定的了解了。那么下期文章我们来深入介绍深浅拷贝的方法。
以上是关于JavaScript浅拷贝与深拷贝以及对象与JSON格式的转换JSON.stringifyJSON.parse的主要内容,如果未能解决你的问题,请参考以下文章