前端【JS】,深拷贝与浅拷贝的区别及详解!
什么是基本类型值和引用类型值?
基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的 对象
var name = "John"; // 基本类型值
var obj = new Object(); // obj 为引用类型值
obj.name = "John";
复制变量
在复制变量中,对于基本类型值来说,两者是互不影响的
var num = 1;
var num1 = num; // num1 = 1;
var num1 = 3; // num还是1,不会变
对于引用类型值来说,复制分为两种,一种是浅拷贝,一种是深拷贝。
一、浅拷贝和深拷贝的区别
对于浅拷贝来说,比如一个数组(数组是一种对象),只要我们修改了一个拷贝数组,那么原数组也会改变
var a = [0,1,2,3,4];
var b = a;
console.log(b); //[0,1,2,3,4]
console.log(a); //[0,1,2,3,4]
b[0] = 2;
console.log(b); //[2,1,2,3,4]
console.log(a) //[2,1,2,3,4]
因为他们引用的是同一个地址的数据!拷贝的时候并没有给b数组创造独立的内存,只是把a数组指向数据的 指针 拷贝给了b!
而深拷贝就与其相反,将会给b数组创造独立的内存,并且将a数组的内容一一拷贝进来,两者互不影响。
二、浅拷贝和深拷贝的原理(参考了大佬的图~~)
浅拷贝和深拷贝一般是对于引用类型值(如对象)来讲的,而基本类型值(如undefined、null、number、string、boolean以及es6新增的Symbol),只要是复制,就一定是另开辟以存储空间(两者互不影响,修改谁都没问题!)!
①基本数据类型存储:名和值都存在栈内存中(两者互不影响,修改谁都没问题!)
如let a = 1;
当let b = a 时,b复制了a,栈内存会新开辟一个内存
所以两者互不影响,修改谁都没问题!
②引用数据类型:名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值(改了b,a也会受到影响。)
b复制了a后,b是引用了a的值的存储地址,而不是把a的值复制了下来!
所以改了b,a也会受到影响。
三、实现深拷贝
单层数组深拷贝(此时的brr跟原来的arr是一模一样的,改变了arr后,brr也是没有发生改变的,因为brr的数据空间是一个新的地址。)
1、Array系统构造函数原型上的concat方法
2、Array系统构造函数运行上的slice方法
多层级数组拷贝 如,var a = [0,1,2,[3,4],5];
a有两层,第一层是[3,4]外围的一层;第二层是[3,4];
注意:如果数组中的数据有引用类型数据,上面两个方法对于数组的拷贝,会将这个引用类型数据的地址也拷贝出来
数组中有引用类型数据的情况下,数组的concat()方法和 slice() 方法,不能实现深拷贝
**这就是浅拷贝,如果数据中都是基本类型数据,就是完全没有联系的两个数据,但是如果数据中引用类型数据,那两个变量还是有一定的联系。
所谓浅拷贝,也就是说,数组或对象中的值如果是基本类型数据,那拷贝后的数据和原数据是完全没有关联,且互不影响的两个数据,如果数组或对象的值是引用类型数据的话,拷贝后的数组或对象中的引用类型的值跟原数据中的引用类型的值,还是存在共享同一地址的现象。
**
所谓深拷贝,就是不管原数据中值是什么类型的数据,拷贝后的新数据跟原数据是完全没有关联的。
方法1、多层级数组拷贝,用递归实现。
利用递归遍历对象或数组
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
for(let i in obj){
objClone[i] = obj[i];
}
return objClone;
}
var a = [0,1,2,[3,4],5];
var b = deepClone(a);
我们得到的b它的首层是另辟存放空间的,也就是上面所说的[3,4]的外面一层存放在另开辟的存储空间中。
但是[3,4]这第二层他仍然是引用数据地址,
因为上面的遍历只遍历了首层!
所以我们应该在上面的代码中加入判断,当碰到数组、对象等时递归函数。
function deepClone(obj){
//定义对象来判断当前的参数是数组还是对象
let objClone = Array.isArray(obj)?[]:{};
//如果obj存在并且为对象
if(obj&&typeof obj == "object"){
for(let key in obj){
if(obj.hasOwnProperty(key)){
//如果obj的子元素为对象,那么递归(层级遍历)
if(obj[key]&&typeof obj[key] == "object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,直接赋值
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
方法2、JSON解析(底层原理也是层级遍历)
利用json数据和json字符串之间的转换
依旧是上面的例子,
var a = [0,1,2,[3,4],5];
var b = JSON.parse(JSON.stringify(a));
b就是拷贝的结果,修改b不影响a。但是这种方法也有缺陷:
1- 无法复制函数
2- 原型链没了,对象就是object,所属的类没了。
总之,赋值是赋值,拷贝是拷贝。
浅拷贝,当对象或数组中的数据都是基本数据类型的时候,两个数据之间完全是独立的,如果对象或数组中的值是引用类型的时候,里面是引用类型的值,还是会保持共同的内存地址;
深拷贝出来的两个数据是完全独立的。
、
、
、
、
、
补充知识点: JSON.stringify()和JSON.parse()
一、 JSON.stringify() 对 JavaScript 对象进行字符串化
JSON 的常规用途是同 web 服务器进行数据交换。
在向 web 服务器发送数据时,数据必须是字符串。
通过 JSON.stringify() 把 JavaScript 对象或数组对象 转换为字符串。
var obj = { name:"Bill Gates", age:62, city:"Seattle"};
var myJSON = JSON.stringify(obj);
var arr = [ "Bill Gates", "Steve Jobs", "Elon Musk" ];
var myJSON = JSON.stringify(arr);
结果将是遵守 JSON 标记法的一段字符串。
myJSON 目前是一段字符串,并准备好送往服务器:
二、 JSON.parse() 将数据转换为 JavaScript 对象。
JSON 通常用于与服务端交换数据。
在接收服务器数据时一般是字符串。
我们可以使用 JSON.parse() 方法将数据转换为 JavaScript 对象。
例如我们从服务器接收了以下数据:
{ "name":"runoob", "alexa":10000, "site":"www.runoob.com" }
我们使用 JSON.parse() 方法处理以上数据,将其转换为 JavaScript 对象:
var obj = JSON.parse('{ "name":"runoob", "alexa":10000, "site":"www.runoob.com" }');
解析数据
JSON 不能存储 Date 对象。
如果你需要存储 Date 对象,需要将其转换为字符串。
之后再将字符串转换为 Date 对象。