JavaScript中的数据类型
要理解深拷贝和浅拷贝,首先需要理解JavaScript的数据类型。可分成两类:
1. 基本数据类型:包括Null、Undefined、Boolean、Number、String、Symbol(ES6新增)。基本类型的变量存放在栈区中,复制变量时会开辟新的栈区,将变量值复制过去,因此,复制的变量和原变量是两个完全独立的变量,操作不会相互影响。
2. 复杂数据类型:Object类型。变量值存放在堆区,同时会在栈区开辟空间存放指向变量值的指针。数据复制时复制的是变量指针,堆中不会再开辟空间。
深拷贝与浅拷贝
浅拷贝和深拷贝针对的是JavaScript中的复杂数据类型。
- 浅拷贝:浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响
- 深拷贝:在堆中重新分配内存,并且把源对象所有属性都进行深拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象完全隔离,互不影响
浅拷贝
直接拷贝原对象的引用
通过赋值新建复杂变量的拷贝时,实际上赋值的是指向同一变量的指针。
let obj1 = { a: 0 };
let obj2 = obj1;
obj2.a = 1;
console.log(obj1.a); //1
原对象拷贝实例,但属性对象拷贝引用
如,当用ES6的Object.assign()方法拷贝对象时,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
let obj1 = {a:0,b:{c:1}};
let obj2 = Object.assign({},obj1);
obj2.a = 2;
obj2.b.c = 2;
console.log(obj1.a); //拷贝的是实例,0
console.log(obj1.b.c); //拷贝的是引用,2
Array对象的slice()和concat()方法实现的也是此类浅拷贝。
let obj1 = ["abc",{a:1}];
let obj2 = obj1.slice();
obj2[0] = "zxc";
obj2[1].a = 2;
console.log(obj1[0]);//abc,外层数组拷贝的是实例
console.log(obj1[1]);//{ a: 2 },复杂数据类型的元素拷贝的是引用
深拷贝
JSON.parse(),JSON.stringify()实现深拷贝
通过JSON.stringify()现将原对象解析为字符串,复制后再反解析为对象。此方法存在一定的局限性:
- 无法复制函数
- 原型链没了,对象就是object,所属的类没了。
-
let obj1 = {a:1,b:{c:1}} let obj2 = JSON.parse(JSON.stringify(obj1)) obj2.a = 2 obj2.b.c = 1 console.log(obj1.a) //1 console.log(obj1.b.c)//1
递归实现深拷贝
function deepCopyByRecursion(obj){ let copyObj if (Object.prototype.toString.call(obj) == '[object Array]') { copyObj = [] }else if(Object.prototype.toString.call(obj) == '[object Object]'){ copyObj = {} }else{ return obj } //当obj为数组时,key代表数组下表;当obj为对象时,key为对象属性名。 for (key in obj){ copyObj[key] = deepCopyByRecursion(obj[key]) } return copyObj } var obj1 = {a:1,b:2} var obj2 = deepCopyByRecursion(obj1); obj1.a = 0; obj1.b = 0; console.log(obj2.a)//1 console.log(obj2.b)//2
迭代实现深拷贝
树的广度优先遍历思想,为了阅读方便,一下代码仅针对深拷贝对象的情况。
function deepCopyByIteration(obj){ let copyObj = {} let objQueue = [obj] let copyQueue = [copyObj] while(objQueue.length > 0){ let sourceTarget = objQueue.shift() let copyTartget = copyQueue.shift() for (key in sourceTarget) { if (Object.prototype.toString.call(sourceTarget[key]) == '[object Object]') { objQueue.push(sourceTarget[key]) copyTartget[key] = {} copyQueue.push(copyTartget[key]) }else{ copyTartget[key] = sourceTarget[key] } } } return copyObj }