文章目录
前言
在了解深浅拷贝之前,我给大家先捋顺一下思路
1、要首先知道什么是基本数据类型,什么是引用数据类型
2、要记住,深浅拷贝是主要针对引用数据类型的
3、请铭记!对于基本数据类型而言其实就是值的拷贝,会开辟相应空间进行存储,互不影响
一、基本类型和引用类型
1.1基本类型
string、number、boolean、null、undefined、symbol
1.2引用类型
Function、Array、Object
二、深浅拷贝怎么理解?
注意:
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
对于基本数据类型而言其实就是值的拷贝,会开辟相应空间进行存储,互不影响
2.1 基本类型拷贝图解
基本数据类型的值都存在栈内存中,拷贝其实就是值的拷贝,会开辟相应的栈空间存储数据,改变新值或者旧值互不影响
如let a = 1;
当let b = a时,b复制了a,栈内存会新开辟一个内存
所以两者互不影响,修改谁都不会影响另一个!
举个栗子:
// 假设我这里设置a=1,然后把a赋值给b,在改变a的值为3
// 那么打印a是3,是修改后的值,而打印b还是1,因为a的改变不影响b
var a = 1;
var b = a;
a = 3;
console.log(a); //3
console.log(b); //1
// 同样的,假设我改变b的值为3,它同样也不影响a的值,a还是1
var a = 1;
var b = a;
b = 3;
console.log(a); //1
console.log(b); //3
2.2 浅拷贝
浅拷贝其实是指拷贝了“指向堆内存的地址”,拷贝后的对象和原对象指向同一个地址,因此修改新对象或者原对象,都会互相干扰
例如:
将一个Object对象所有的属性和属性值复制给另一个Object对象。这样导致的后果是,新对象和老对象相同的Object类型的属性值在内存中的指向是一样的,也就是新对象和老对象的Object类型的同一属性值指向的是同一块内存。因此修改新对象或者原对象,都会互相干扰。
2.1.1 浅拷贝图解( 引用数据类型)
地址在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址,指向堆内存中存储的值
例如:我有一个引用类型的a,它的地址在栈内存中,但是数据则是存储在堆内存中,栈内存会提供一个引用地址,指向堆内存中存储的值
b复制了a后, b其实是生成了一个新地址,地址指向和a地址指向为同一个堆数据,而不是把a的值复制了下来,其实他们用的还是同一组数据,并没有拷贝出新数据
所以改了b,a也会受到影响。
举个栗子:
// 假设我有一个引用类型arr的数据,arr1拷贝了arr,
// 然后我把arr的name改成了李四,那么打印arr和arr1都是修改后的值
// 这是因为,arr1在拷贝时,并没有开辟空间去存储arr的值,只是单纯引用了arr值的存储地址而已,地址都指向了同一组数据
var arr = [{ name: "张三" }, { age: "10" }];
var arr1 = arr;
arr[0].name = "李四";
console.log(arr); //[{ name: "李四" }, {age: "10" }];
console.log(arr1); //[{ name: "李四" }, {age: "10" }];
// 同样的,我如果改变arr1的name,打印结果也是修改后的
var arr = [{ name: "张三" }, { age: "10" }];
var arr1 = arr;
arr1[0].name = "李四";
console.log(arr); //[{ name: "李四" }, {age: "10" }];
console.log(arr1); //[{ name: "李四" }, {age: "10" }];
2.3 深拷贝
深拷贝是基于引用型出来的一种技术手段,不仅会在栈中开辟另一块空间,还会在堆内存中开辟另一块空间存储引用类型的真实数据。拷贝后的对象和原对象不指向同一个地址,因此修改新对象或者原对象,都不会互相干扰
例如:
将一个Object对象的内容完全拷贝一份给新对象。修改原对象的属性或者属性值,都不会影响新对象。原对象和新对象是完全独立的,互不影响。
2.3.1 深拷贝图解( 引用数据类型)
例如,我有一个obj1的引用类型的对象,它的地址在栈空间中,而数据存储在堆空间里面,栈内存会提供一个引用地址,指向堆内存中存储的值
然后我设置了一个新的对象obj2,用来深拷贝obj1对象
然后,obj2会在堆内存中也开辟一个新的堆内存专门为Obj2存放值的地方,并拷贝一份obj1的值放进去,这时候,obj1和obj2的地址指向不是同一个,就像基本类型那样。
而当我们Obj1[0]=9时进行数组修改时,由于Obj1与Obj2指向的是不同的地址,所以Obj2不受影响,这就是深拷贝。
三、浅拷贝的实现方式
重要的话说三遍!!!
如果数组元素是基本类型,就是值的拷贝,会开辟存储空间,互不影响,
如果数组元素是基本类型,就是值的拷贝,会开辟存储空间,互不影响,
如果数组元素是基本类型,就是值的拷贝,会开辟存储空间,互不影响,
// 如果数组元素是基本类型,就会拷贝一份,互不影响,
var arr = [1, 2, 3];
var brr1 = arr.concat();
var brr2 = arr.slice();
var brr3 = [...arr];
var brr4 = Object.assign([], arr);
var brr5 = Array.from(arr);
arr.push(4);
console.log(arr); // [1, 2, 3, 4]
console.log(brr1); //[1, 2, 3]
console.log(brr2); //[1, 2, 3]
console.log(brr3); //[1, 2, 3]
console.log(brr4); //[1, 2, 3]
console.log(brr5); //[1, 2, 3]
如果是引用数据类型,就会只拷贝对象和数组的引用,无论对新旧数组的哪一个进行了修改,两者都会发生变化。
var arr = [{ name: "张三" }, { age: "10" }]; //原数组
var newArr1 = arr.slice();
var newArr2 = arr.concat();
var newArr3 = Object.assign([], arr);
var newArr4 = Array.from(arr);
arr[0].name = "李四";
arr[1].age = "20";
console.log(arr); // [{ name: "李四" }, { age: "20" }]
console.log(newArr1); //[{ name: "李四" }, { age: "20" }]
console.log(newArr2); //[{ name: "李四" }, { age: "20" }]
console.log(newArr3, "newArr3"); //[{ n张三ame: "李四" }, { age: "20" }]//浅拷贝
console.log(newArr4, "newArr4"); //[{ name: "李四" }, { age: "20" }]//浅拷贝
3.1 浅拷贝实现的方法总结
1、concat( )
Array的concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
2、slice( )
Array的slice方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
3、拓展运算符 (…)
扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
4、Object.assign(‘目标对象’,‘源对象’)
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
5、Array.from( )
四、深拷贝的实现方式
4.1 JSON.parse(JSON.stringify(obj))
通过JSON.stringify把对象转成基本类型,再用JSON.parse把字符串(基本类型)转成一个全的新的对象;JSON.parse这个方法会在堆中开辟新的空间。所以这里是利用了JSON.parse的这个特性做到的深拷贝
实际项目开发过程中,这种方法其实使用的最多缺点:不会拷贝对象上的 value 值为 undefined 和 函数的键值对
//数组形式的数据展示
let list = [{ name: "张三" }, { age: "20" }];
let copyList = JSON.parse(JSON.stringify([...list]));
list[0].name = "李四";
list[1].age = "30";
console.log(list); // [{ name: "李四" }, { age: "30" }]
console.log(copyList); // [{ name: "张三" }, { age: "20" }]
//对象形式的数据展示
let list = { name: "张三", age: "20" };
let copyList = JSON.parse(JSON.stringify({...list}));
list.name = "李四";
list.age = "30";
console.log(list); // [{ name: "李四" }, { age: "30" }]
console.log(copyList); // [{ name: "张三" }, { age: "20" }]
4.2 _.cloneDeep
函数库lodash,也有提供_.cloneDeep用来做深拷贝,安装后使用
官网:https://www.lodashjs.com/
缺点:需要下载依赖包,对性能有影响
//数组形式的数据展示
let lodash = require('lodash');
let list = [{ name: "张三" }, { age: "20" }];
let copyList = lodash.cloneDeep(list);
list[0].name = "李四";
list[1].age = "30";
console.log(list); // [{ name: "李四" }, { age: "30" }]
console.log(copyList); // [{ name: "张三" }, { age: "20" }]
//对象形式数据展示
let lodash = require('lodash');
let list = { name: "张三" , age: "20" };
let copyList = lodash.cloneDeep(list);
list.name = "李四";
list.age = "30";
console.log(list); // [{ name: "李四" }, { age: "30" }]
console.log(copyList); // [{ name: "张三" }, { age: "20" }]
4.3 递归实现
缺点:写起来比较繁琐,容易出bug,不是很建议使用
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
//判断ojb子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
} else {
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a = [1, 2, 3, 4],
b = deepClone(a);
a[0] = 2;
console.log(a, b) // [2,2,3,4] [1,2,3,4]