一. 基础知识
1.1 数据类型
javascript有几种数据类型。这是一个很简单的问题,但是也是面试官喜欢问的致命问题,因为基本上答错一个就印象分大打折扣了。在Javascript中一共只有以下7种数据类型。
Number
String
Boolean
Null
Undefined
Symbol
Object
其中前面6种类型是原始数据类型,而Object是引用数据类型。我更喜欢把前面6种称之为简单数据类型,而把Object称之为复杂数据类型。因为简单数据类型没有子类型了,不可以再进行拆分了,而复杂数据类型还有子类型,比如Array,Function,RegExp,Date等对象,正是因为这些子类型的不同导致了深拷贝的各种问题。这就是为什么很多人在回答有哪些基本数据类型时会把Array和Function答进去。事实上他们只是Object的子类型,并不是基本数据类型。数据类型的不同,会导致在内存中的存储方式的不同,如果是简单数据类型,存储在栈空间中,存储的是一个值;如果是复杂数据类型,存储在堆空间中,存储的是一个引用。正是这种存储方式的差异,导致了浅拷贝和深拷贝的区别。
1.2 浅拷贝和深拷贝
我们先来明确一下到底什么是浅拷贝什么是深拷贝。
浅拷贝: 如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以修改新拷贝的对象会影响原对象。
let obj = {
id:1,
info:{
name:"hello",
age:24
}
}
let obj2 = obj; // 赋值就是一个浅拷贝
obj2.id = 3;
console.log(obj.id); // 3
深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
let obj = {
id:1,
info:{
name:"hello",
age:24
}
}
let obj2 = JSON.parse(JSON.stringify(obj)); // 这里实现深拷贝 暂时记住就好
obj2.id = 3;
obj2.info.name = "刘亦菲";
console.log(obj.id); // 1
console.log(obj.info.name); // hello
上面的代码中obj2是通过深拷贝obj1得到的,修改obj2的属性,发现Obj1的属性不会跟着修改。这是深拷贝。
二、深拷贝的实现
在上面的深拷贝的代码示例中,我使用了 JSON.parse(JSON.stringify()) 实现了一个深拷贝。这就是日常开发中使用较为频繁的一个深拷贝方法,它可以实现一些不是那么复杂的数据类型的深拷贝。示例:
let num = 24;
let bool = true;
let obj = {
id:1
info:{
name:"hello",
age:24
}
}
let num1 = JSON.parse(JSON.stringify(num))// num1就是num的深拷贝 虽然简单的数据类型这种拷贝没啥意义
let bool1 = JSON.parse(JSON.stringify(bool))// num1就是num的深拷贝 虽然简单的数据类型这种拷贝没啥意义
let obj2 = JSON.parse(JSON.stringify(obj))// 复杂数据类型也可以使用JSON.parse(JSON.stringify(obj))
但是这种方法存在一些缺点,由于它是依赖于JSON,因此它不支持JSON不支持的其他格式,通过JSON的官网可知,JSON只支持object,array,string,number,true,false,null这几种数据或者值,其他的比如函数,undefined,Date,RegExp等数据类型都不支持。对于它不支持的数据都会直接忽略该属性。
1. 对象中不能有函数,否则无法序列化
2. 对象中不能有undefined,否则无法序列化
3. 对象中不能有RegExp正则,否则无法序列化
4. Date类型数据会被转化为字符串类型
如果对象中存在Date类型的数据,会被转换成字符串,从而丢失Date的一些特性,比如时间格式化等方法。
5. 对象不能是环状结构的,否则会导致报错
所谓环状结构的对象,就是对象的属性又指向了自身,window就是最常见的一个环状对象。
let obj = {name:'hello'}
obj.self = obj // self属性又指向了obj对象,形成了一个换
这种环状结构的对象,在使用JSON.parse(JSON.stringify)深拷贝时会报错。
小结:从上面的分析中,我们可以看到 JSON.parse(JSON.stringify()) 虽然能够深拷贝一个对象,但是存在很大的局限性,对于复杂的对象就不适用了。因此,我们需要采用另外的方式来实现深拷贝,也就是通过递归的方式手动实现深拷贝。
三、 终极大法(拿来即用)
较为完整的深拷贝。最终的实现函数如下
function deepClone(target,cache = new Map()){
if(cache.get(target)){
return cache.get(target)
}
if(target instanceof Object){
let dist ;
if(target instanceof Array){
// 拷贝数组
dist = [];
}else if(target instanceof Function){
// 拷贝函数
dist = function () {
return target.call(this, ...arguments);
};
}else if(target instanceof RegExp){
// 拷贝正则表达式
dist = new RegExp(target.source,target.flags);
}else if(target instanceof Date){
dist = new Date(target);
}else{
// 拷贝普通对象
dist = {};
}
// 将属性和拷贝后的值作为一个map
cache.set(target, dist);
for(let key in target){
// 过滤掉原型身上的属性
if (target.hasOwnProperty(key)) {
dist[key] = deepClone(target[key], cache);
}
}
return dist;
}else{
return target;
}
}