一、javascript深拷贝浅拷贝相关知识点
1.javascript变量包含两种不同数据类型的值:基本类型和引用类型。
- 基本数据类型指的是简单的数据段,包括es6里面新增的一共是有6种,具体如下:number、string、boolean、null、undefined、symbol。
- 引用数据类型指那些可能由多个值构成的对象,只有一种如下:object。
在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。
2.javascript的变量的存储方式:栈(stack)和堆(heap)。
- 栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
- 堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。
引用类型的值是保存在内存中的对象。JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。 在操作对象时, 实际上是在操作对象的引用而不是实际的对象。
3.基本类型与引用类型最大的区别实际就是 传值与传址 的区别
- 值传递:基本类型采用的是值传递。
- 地址传递:引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量。
4.浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
- 基本数据类型Number(赋值操作)
let a=1;
let b=a;
b //1
b=2;
b //2
a //1
- 数组
let arr1 = [1,2,3];
let arr2 = arr1;
arr2 //[1,2,3]
arr2.push(4);
arr2 //[1,2,3,4]
arr1 //[1,2,3,4]
首先栈内存arr1会指向堆内存里的数组,栈内存的arr1保存的是数组的引用,也就相当于内存地址,arr2=arr1,会把arr1的引用赋给arr2,所以arr2也有了数组的引用,此时arr1和arr2指向的是同一个数组,因此一个数组的改变会影响另一个数组的值。
- 对象
let obj1={count:1,name:'grace',age:1};
let obj2 = obj1;
obj2 //{count:1,name:'grace',age:1}
obj2.count=2;
obj1 //{count:2,name:'grace',age:1}
obj2 //{count:2,name:'grace',age:1}
综上所述,如果是基本数据类型,直接进行赋值操作,这样就相当于在栈内存中重新开辟了一个新的空间把值传递过去;如果是引用类型的值传递,进行的就是浅拷贝,浅拷贝赋值的只是对象的引用,如上述obj2=obj1,实际上传递的只是obj1的内存地址,所以obj2和obj1指向的是同一个内存地址,所以这个内存地址中值的改变对obj1和obj2都有影响。
5.深拷贝
深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上,所以对一个对象的修改并不会影响另一个对象
二、js中数组深拷贝的方法
1.for循环遍历+push
let arr1 = [1,2,3];
let arr2 = copyArr(arr1);
function copyArr(arr){
let res=[];
for(let i=0,length=arr.length;i<length;i++){
res.push(arr[i]);
}
return res;
}
2.slice(start,end)
let arr1 = [1,2,3];
let arr2 = arr1.slice(0);
3.concat方法
适用 单层 数组嵌套的深拷贝
let arr1 = [1,2,3];
let arr2 = arr1.concat();
4.扩展运算符
适用 单层 数组嵌套的深拷贝
let arr1 = [1,2,3];
let [...arr2] = arr1;
5.Array from(ES6中的方法)
let arr1 = [1,2,3];
let arr2 = Array.from(arr1);
三、js中对象深拷贝的方法
1.for…in循环
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
let obj1={name:'小花',age:18,sex:'女'}
let obj2={}
for(let key in obj1) {
obj2[key]=obj1[key]
}
console.log(obj2);
2.利用JSON
通过JSON.stringify() 和 JSON.parse() 将对象转为字符串之后在转为对象。
这种简单粗暴的方式有局限性,当值为undefined、function、symbol会在转换过程中被忽略。
let obj1={name:'小花',age:18,sex:'女'}
let obj2 = JSON.parse(JSON.stringify(obj1))
console.log(obj2);
3.扩展运算符(ES6)
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
let obj1={name:'小花',age:18,sex:'女'}
let obj2 = {...obj1}
console.log(obj2);
4.Object.assign() 方法
利用Object.assign(), 第一个参数必须是空对象
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
let obj1={name:'小花',age:18,sex:'女'}
let obj2=Object.assign({},obj1)
console.log(obj2);
5.利用循环和递归的方式
在循环递归中需要注意设置临界值(typeof obj[key] == ‘object’),否则会造成死循环。
循环递归可以处理对象中嵌套数组或对象的问题。相当于第三种方法的优化。
function deepClone(obj, newObj) {
var newObj = newObj || {};
for (let key in obj) {
if (typeof obj[key] == 'object') {
newObj[key] = (obj[key].constructor === Array) ? [] : {}
deepClone(obj[key], newObj[key]);
} else {
newObj[key] = obj[key]
}
}
return newObj;
}