要了解深浅拷贝,需要先弄懂 JS 不同数据类型是如何保存在内存中的
栈内存和堆内存
JS 数据类型分为基本数据类型和引用类型,前者保存在栈内存,后者保存在堆内存中。
根本原因在于–
- 保存在栈内存的必须是大小固定的数据
- 引用类型的大小不固定,只能保存在堆内存中,但是可以把它的地址写在栈内存中以供我们访问。
如果是基本数据类型,****则按值访问****,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址来操作实际对象
下面给两张图,就很清楚了
基本数据类型
而对于引用类型的复制–
复制的只是引用地址
1 | var color1 = ["red", "green"]; |
可以看到,修改 color1,color2 也变了,这是因为 color1 与 color2 指向堆内存中同一地址的同一对象。二者就是一个东西
引用类型
深拷贝和浅拷贝
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
- 浅拷贝方法很简单:
- 比如直接赋值
- [].slice()方法:数组的一个浅拷贝
- 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
弄明白深浅拷贝区别之后,我们再来分析一个“假的深拷贝”
1 | function simpleClone(initalObj) { |
- 以上代码中,虽然创建了一个新的对象 copy,然后将被复制对象 obj1 的各属性挂载到这个 copy 上,此时 copy 和 obj1 确实是两个对象,不共享同一内存空间。
但是–
- 它们之中的引用类型 o,仍旧指向同一块内存。因此当修改 obj1 的对象属性 o 时,copy 之中的 o 也对应得变了
以上代码是个假的“深拷贝”!,它只使用于对象属性都为基本数据类型时
因此,当复制一个存在引用类型属性的对象时,该方法不适用!
实现深拷贝的几种方法
1.递归调用浅拷贝(深度优先)
- 第一次调用时,只需要传入要深拷贝的那个对象,此时形参 finalObj 无实参,等同于 var obj = {},该{}即为要返回的那个拷贝对象(它是最外层)
- 此后的每次递归调用,第 12 行代码都会为那一层的引用类型创建新的[]/{}(就是弄个新内存),以此实现深拷贝
- prop 用于遍历被拷贝对象的各属性以及其中嵌套的每层引用类型的属性
- finalObj 用于接收 obj[i],其中记录了被拷贝对象中引用类型的嵌套层次
- 递归的过程类似于深度优先遍历
1 | function deepClone(initalObj, finalObj) { |
2.借助 JSON 对象
- JSON.stringify 会将对象变成 JSON 格式的字符串,
- 此时为基本类型,直接复制即可
- JSON.parse 会将 JSON 格式的字符串转化为JS 对象,会为该对象创建新的内存空间
1 | function deepClone2(obj) { |
3.jQuery 的 extend 方法
1 | var array = [1, 2, 3, 4]; |
lodash 函数库提供的深拷贝方法
lodash 是一个函数库,里面有很多封装好的,功能完善,性能优良的函数,可以直接 require 然后使用
1 | // 函数库lodash,有提供_.cloneDeep用来做 Deep Copy。 |
什么时候使用?
当你希望在多处使用某一个引用类型的值,而不希望其中一处改变会影响到另一处。这时,就可以使用深拷贝
参考:
评论