深拷贝、浅拷贝在实际开发中经常应用到,这里记录一些理解。
¶一、内存中的堆栈(heap、stack)
堆和栈都是内存中用来存储的区域:栈是自动分配的内存,由系统自动释放;堆是动态分配的内存,大小不定同时也不会自动释放。深拷贝和浅拷贝的区别也在于两者在内存中的存储类型有所不同。
介绍一下基本数据类型和引用数据类型的概念,基本数据类型
包括:String、Number、Boolean、null、undefined 五类(es6 新引入 Symbol,代表独一无二的值),引用数据类型
统称为 Object 类型,包括:对象、数组、Date、RefExp、函数等。
- 栈:基本数据类型存放在栈里面,数据段简单,数据大小也确定,占用内存空间大小确定,是直接按值存放的,可以直接访问。
- 堆:引用数据类型存放在堆里面,object 实际上是一个存放在栈内存的指针,这个指针指向的是堆内存中的地址。
注意
- 1、js 中基本数据类型的值不可变、引用数据类型的值可变
- 2、操作字符串返回的都是新字符串,并没有改变原有的字符串
- 3、基本数据类型比较值,引用数据类型比较引用是否指向同一个对象
¶二、赋值(Copy)
- 基本数据类型:
赋值
,赋值之后两个变量互不影响 - 引用数据类型:
赋址
,两个变量具有相同的引用,指向同一个对象,相互之间有影响。
举个赋值的例子:
1 | let a = 'old' |
举个赋址的例子:
1 | let a = [1, 2, 3, 4, 5, 6] |
¶三、浅拷贝(ShallowCopy)
拷贝原始对象,如果原始对象的属性是基本数据类型,拷贝的就是基本数据类型的值;如果原始对象的属性是引用数据类型,那么拷贝的就是引用数据类型的内存地址,如果两个对象中的某一个改变属性值从而影响内存地址,则另一个对象也会受到影响。
¶1、Object.assign()
Object.assign() 可以将多个对象的属性复制到目标对象,返回目标对象。下面的代码中,改变对象 a 之后,b 对象的属性中,基本数据类型保持不变,但是引用数据类型发生了对应的变化。
1 | let a = { |
¶2、… 解构
展开语法(Spread syntax) 通过实验可以看出效果和Object.assgin()
一致。
1 | let a = { |
¶3、Array.slice()
slice() 方法的效果可以说明slice()
方法是浅拷贝。
1 | let a = ['old', true, [0, 1]] |
¶4、Array.concat()
concat() 方法也是浅拷贝,同slice()
。
¶四、深拷贝(DeepCopy)
在深拷贝时,会复制原始对象所有的属性,并分配新的内存地址,相比浅拷贝性能消耗较大且速度较慢。拷贝前后的两个对象互不影响。
¶1、JSON.parse(JSON.stringify(object))
JSON.parse()、JSON.stringify() 可以达到下述代码中的完全改变 a 而不影响 b 的效果。
1 | let a = { |
数组的深拷贝也可以进行,但是JSON.parse(JSON.stringify(object))
有几个问题:
- 会忽略 undefined、symbol 和 function
- 处理循环引用的对象会报错
- new Date() 的转换结果不正确,可以考虑转换成字符串或者时间戳再深拷贝
- 不能处理正则
¶2、递归写法
- 基础类型
- 引用类型
RegExp、Date、函数 不是 JSON 安全的
会丢失 constructor,所有的构造函数都指向 Object
破解循环引用
1 | function deepCopy (obj) { |