js深拷贝浅拷贝与lodash
深拷贝和浅拷贝
ref:JS传递参数时的内部存储逻辑
JS 變數傳遞探討:pass by value 、 pass by reference 還是 pass by sharing?
在这个问题前,有另一个问题就是Array类型的拷贝。
怎么样进行Array的深拷贝
这个问题是我在leetcode时候发现的,其实就在于,当我实现迭代的时候,函数需要改变参数目的地址的实际内容,这时候就会在想对于Array的储存结构是怎么样的,修改函数内部是否会一同影响外部的变量呢,还是说只是单纯地操作到值上,并没有对初始的内存地址进行变更。
ref: https://dev.to/samanthaming/how-to-deep-clone-an-array-in-javascript-3cig
首先对于 array,显然是浅拷贝
1 | let array = [1, 2, 3] |
可以使用解析式一种方式去clone数组
1 | let array = [1, 2, 3] |
而这种方法会在嵌套数组当中不起效
1 | let nestedArray = [1, [2], 3]; |
当我们改动嵌套数组的时候,源数组也会收到affacted。因此我们可以通过这样的方式对数组进行深拷贝
1 | let nestedArray = [1, [2], 3]; |
但是 JSON.parse(JSON.stringify(nestedArray))
这样的方法并不是万能的,会导致这样的问题。比如说undefined类型会转化为null,DOM类型会转化成空对象,而Date类型会转化为String。
1 | function nestedCopy(array) { |
JSON.stringify/parse only work with Number and String and Object literal without function or Symbol properties.
因此这个时候可以引入lodash,帮助我们去解决这个问题
1 | const lodashClonedeep = require("lodash.clonedeep"); |
当然,还有另一种方法则是嵌套拷贝
1 |
|
如果对于js的深浅拷贝该怎么解释呢
阅览,我十分赞同这么一个说法:不论是传引用还是传值,定义都可以归结为 传值
即: pass by value,为什么可以这么归结呢?
原始数据类型传递
对于js在储存结构里不变的Number、String等,在传参数的时候,属于之间传递值。
1 | function test(primitiveData) { |
这个过程的执行顺序为
- 声明function test()
- 将数值5赋给primitiveData
- 将primitiveData累加5
- 因为primitiveData相当于一个新的对象,因此不会修改原来a的值
引用数据类型传递
而对于引用类型,如Object、Array等,可以视作为传递引用。因为对于非原始数据类型,引用变量储存的数据是真实数据所在的地址,其所经历过的过程为。
1 |
|
- 声明function test()
- 将对象a的数值地址传递objectData
- 修改对应数值地址的变量属性number为10
- 源对象a的数值同样也被修改
拷贝过程的内存地址走向
为什么会有这样的问题呢?因为js在储存数据的时候,对于原始数据类型跟引用类型方式稍有不同。
在原始数据类型当中,这些value会存在栈中。因此,在使用=进行赋值的时候,会直接将栈区的数据复制一份。而对于引用类型,栈区则是会存储其处于堆区,因此在传值的时候,则是会把堆区这一块数据地址传递进去。
所以,这也是为什么说将Object引用类型传递会与源数据共享一个地址。
三点(…)运算符 & Object.assign
有很多方式可以去解决深拷贝的问题,最简单的可以使用JSON库的序列化和反序列进行,不过这种方式会对比如说DOM类型,undefined类型不友好。
而对于只有一级属性的时候,则是可以通过…运算符或者 Object.assign()
1 | const foo = { |
总结
我的想法是,对于JS而言,pass by reference和pass by value都可以视为pass by value,因为在进行赋值操作的时候,JS都会复制一份栈区的数据。而对于Object类型栈区所存数据为「address地址」,而对于Primitive Type而言,则是直接存入原始数据。