最近碰到一个问题,比如我在搜索条件参数是如下所示
searchParams
但是呢,我在向后台发送请求的时候,我需要带一个 id,以及这个 searchParams,但是又不能传递多个参数,只能传递一个 object 对象过去,又不影响原有的 searchParams,所以想到的方式只有深拷贝
一直使用的是 JSON.parse(), JSON.stringify() 但是好像之前看过一篇文章是讲述会破坏什么继承链之类的,性能不太好,所以今天特地来研究下深浅拷贝这个问题
- 基础认识---基本类型
- 基础认识---引用类型
- 浅拷贝的实现-对象&&数组
- 深拷贝的实现-对象&&数组
- 深拷贝的实现- ES6扩展运算符实现对象&&数组的深拷贝
- 深拷贝的实现-递归的方法
- 深拷贝的实现-JSON.stringify/parse的方法
基础认识:
对于js的对象的深拷贝和浅拷贝,必须先提到的是JavaScript的数据类型, 我们先来理解一些js基本的概念 —— Javascript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Number和String,并且基本类型存放在栈内存。还含有一种复杂的数据类型(也叫引用类型)存放在堆内存,就是对象(Object,Array)。 堆内存用于存放由new创建的对象,栈内存存放一些基本的类型的变量和对象的引用变量。
注意Undefined和Null的区别,Undefined类型只有一个值,就是undefined,Null类型也只有一个值,也就是null
Undefined 其实就是已声明未赋值的变量输出的结果
null 其实就是一个不存在的对象的结果。
JS 中的浅拷贝与深拷贝,只是针对复杂数据类型(Object,Array)的复制问题。浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别
1. 对于基本数据类型
他们的值在内存中占据着固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的副本,并且我们不能给基本数据类型的值添加属性
var
![668c0966bf3ddbb476097724d6c134cb.png](https://i-blog.csdnimg.cn/blog_migrate/f57c6a3c4bea308d1bf2d78595546eaa.png)
上面的代码中,a是基本数据类型(Number), b是a的一个副本,它们两者都占有不同位置但相等的内存空间,只是它们的值相等,若改变其中一方,另一方不会随之改变
2. 对于引用类型
复杂的数据类型即是引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针地址而已,因此两个变量最终都指向同一个对象
var
![d812a002c0b99fdf479c2dec568960a1.png](https://i-blog.csdnimg.cn/blog_migrate/a63e32b06f9ea6bc52c6760a0d71f481.png)
![709a7f66df5f730506ca383612297bad.png](https://i-blog.csdnimg.cn/blog_migrate/983af3b4b9a331510a1496b56dddf190.png)
注:0x123指针地址
我们可以看到obj赋值给obj2后,但我们改变其中一个对象的属性值,两个对象都发生了改变,根本原因就是obj和obj2两个变量都指向同一个指针,赋值时只是复制了指针地址,它们指向同一个引用,所以当我们改变其中一个的值就会影响到另一个变量的值。
一、深拷贝和浅拷贝的区别
浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变
浅拷贝的实现
浅拷贝的意思就是只复制引用,而未复制真正的值,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的。
(1)对象的浅拷贝: 上面的代码就是对象的浅拷贝的例子
(2)数组的浅拷贝:
var
![619bd394444a2115bb1a6c58e71f9070.png](https://i-blog.csdnimg.cn/blog_migrate/5d4b55d527830d1414eae1b551f15f1f.png)
上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 arr2 和 arr 改变,arr 和 arr2 也随着发生了变化
深拷贝的实现
(1)数组的深拷贝 对于数组我们可以使用slice() 和 concat() 方法来解决上面的问题
注意:(slice() 和 concat()对数组的深拷贝是有局限性的。)
**slice **
var
![c21c351eb09476ca2e38dc2e453e7b3d.png](https://i-blog.csdnimg.cn/blog_migrate/7eeec29b6b03c51f7a25a3ebc97ee0fa.png)
concat
var
![f4e2d145c171df78a8dde301b0c16aa1.png](https://i-blog.csdnimg.cn/blog_migrate/a2fa3b4824fd5619bc3fcaa55363042b.png)
针对上面说的slice() 和 concat()对局限性,我们可以继续看下面的例子:
var
![33f8c8bdd81b9b2fb65c62e8c67ce8ce.png](https://i-blog.csdnimg.cn/blog_migrate/5e312e5fe6e954fdf4c2e95e9e967ce8.png)
可以发现使用.concat()和浅复制的结果一样,这是为什么呢,那slice()会出现同样的结果吗?继续看写看例子
var
![19d3d0b72f1427cb536f586442ea66e0.png](https://i-blog.csdnimg.cn/blog_migrate/b77b18a71222d88a585b3cb31938a28f.png)
var
![94d19748fed342202a5426834c15f70a.png](https://i-blog.csdnimg.cn/blog_migrate/3b66971b35efc2673495a36144047421.png)
哟,也是出现同样的结果呀,原来由于上面数组的内部属性值是引用对象(Object,Array),slice和concat对对象数组的拷贝,整个拷贝还是浅拷贝,拷贝之后数组各个值的指针还是指向相同的存储地址.因此,slice和concat这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝
注(补充点):
- arrayObj.slice(start, [end]) 该方法返回一个 Array 对象,其中包含了 arrayObj 的指定部分。不会改变原数组
- arrayObj.concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。 其实也就是下面实现的方式,但还是用上面的方法来实现比较简单高效些
function
ES6扩展运算符实现数组的深拷贝
var
![8aa47cd4bd63344c408759a29aff277d.png](https://i-blog.csdnimg.cn/blog_migrate/c2aa8e70fdc7785cc0c520f1318fbfea.png)
(2)对象的深拷贝 对象的深拷贝实现原理: 定义一个新的对象,遍历源对象的属性 并 赋给新对象的属性 主要是两种:
- 利用递归来实现每一层都重新创建对象并赋值
- 利用 JSON 对象中的 parse 和 stringify
- ES6扩展运算符实现对象的深拷贝
var
![6f4eae53b80fc4c0bb966db00956f053.png](https://i-blog.csdnimg.cn/blog_migrate/69e60e3c9a29877f5fa7f8efaaf69ff5.png)
var
![e4feed3fe6a941f066db4c25bd75cb5f.png](https://i-blog.csdnimg.cn/blog_migrate/d60773afd0d65207112e9dac9eeac2f1.png)
obj2是在堆中开辟的一个新内存块,将obj1的属性赋值给obj2时,obj2是同直接访问对应的内存地址。
递归的方法
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:
function
那我们来试试个例子:
![93edff70d6c500eef956517339363877.png](https://i-blog.csdnimg.cn/blog_migrate/4044a179f6e190a919ab287c598f2970.png)
对象与Json相互转换
我们先看这两种方法:SON.stringify/parse的方法
The JSON.stringify() method converts a JavaScript value to a JSON string.
**JSON.stringify **是将一个 JavaScript 值转成一个 JSON 字符串。
The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.
**JSON.parse **是将一个 JSON 字符串转成一个 JavaScript 值或对象。
JavaScript 值和 JSON 字符串的相互转换。
来一步步看下面的封装层例子:
function
未封装和封装的进行比较:
const
虽然上面的深拷贝很方便(请使用封装函数进行项目开发以便于维护),但是,只适合一些简单的情景(Number, String, Boolean, Array, Object),扁平对象,那些能够被 json 直接表示的数据结构。function对象,RegExp对象是无法通过这种方式深拷贝。
注意:
var
举例:
const
发现在 cloneObj 中,有属性丢失了。。。是什么原因? 在 MDN 上找到了原因:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
undefined、function、symbol 会在转换过程中被忽略。。。 明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝