了解Javascript深拷贝与浅拷贝
will.wei 7/19/2021 深拷贝
# 了解Javascript深拷贝与浅拷贝
# 深拷贝、浅拷贝概念
在学习JS时可能经常听到深拷贝、浅拷贝这两个概念,那么它们的概念和区别时什么呢?
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是指向同一块内存;
深拷贝会另外创造一个一模一样的对象,新旧对象所指的内存不同,所以深拷贝修改新对象不会改到旧对象;
# 常用数据类型的深浅拷贝判断
在上一篇文章——javascript数据类型判断 中,提到过JS主要有两类数据类型:
- 基本数据类型
(Boolean, Number, String, undefined, BigInt, Symbol, Null )
- 复杂数据类型
(Object)
其中,基本数据类型是保存在栈内存的,复杂数据类型是保存在堆类型的,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中;
所以,当基本数据类型拷贝时,只需要在栈中分配一个新值和旧值一样就行,根据深浅拷贝的概念,所以基本数据类型的拷贝是深拷贝;
而对于复杂数据类型拷贝时,系统也为新的变量在栈内存中分配了一个值,但这个值指向的堆中的对象是不变的,即和旧对象具有同一个对象的引用,那当新对象的值改变时,会改变引用的指向的堆内存,旧对象也会一起改变,所以复杂数据类型的引用是浅拷贝;
3. ### 深拷贝的实现以及手写深拷贝方法
# JSON.parse(JSON.stringfy())方法
这种方法最为简单暴力,对于简单的JSON格式对象是可以的,但是还是会有如下缺点:
- 这样会丢失原对象的constructor,失去之前的构造函数
- 对于set,map,Date,RegExp等特殊对象会在转换的时候丢失
# 函数库lodash的_.clone方法
这种方法引入lodash库就可以了,但是面试时不能这么说啊,所以还是老老实实琢磨下怎么自己手写个像样的深拷贝吧
# 递归实现深拷贝
既然这样,那就自己手写一个深拷贝吧
首先思路如下:
- 如果是基本类型:无需继续拷贝,直接返回;但考虑到ES6新加入的数据类型Symbol,在for in 中是取不到的,所以使用Reflect.ownKeys()来遍历该属性
- 如果是复杂类型,则需要递归取值
- 循环引用的情况,如
obj.param = obj
使用WeakMap做个缓存取值
//手写深拷贝方法deepClone
function deepClone(obj, map = new WeakMap()){
// 如果为基本类型
if(!obj || typeof obj !== 'object'){
return obj
}
// 判断是否为数组
const cloneObj = Array.isArray(obj)? [] : {};
// 判断是否为循环引用
if(map.get(obj)){
return map.get(obj)
}
map.set(obj, cloneObj)
//递归取值赋值
for(let key of Reflect.ownKeys(obj)){
cloneObj[key] = deepClone(obj[key],map)
}
return cloneObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这个深拷贝基本实现了对基本对象的深拷贝,但还有一些问题:
- 如果对象的value值为set,map,Date,RegExp这些没有做特殊处理,不能拷贝
- 但如果是面试,手写到这些也就够了,其他这些可以使用构造函数重新构造一个