概念
首先我们要了解一下什么是栈和堆:
栈(stack
):栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间。(基本类型:String
,Number
,Boolean
,Null
,Undefined
)
堆(heap
):动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。(引用类型:Function
,Array
,Object
)
在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据存储在堆内存中。
栈数据
var a = 3;
var b = a;
b = 5;
console.log(a); //3
console.log(b); //5
上面这段代码可以看到的是对于基本类型来说,我们将一个基本类型的值赋予
a
变量,接着将a
的值赋予变量b
;然后我们修改b
;可以看到b
被修改了,而a
的值没有被修改,两个变量都使用的是独立的数据;
堆数据
var obj1 = {
a: 1,
b: 2,
c: 3
}
var obj2 = obj1;
obj2.a = 5;
console.log(obj1.a); //5
可以看到的是,两个对象的值全部被修改了。对象是引用类型的值,对于引用类型来说,我们将
obj1
赋予obj2
的时候,我们其实仅仅只是将obj1
存储在栈堆中的的引用地址赋予了obj2
,而两个对象此时指向的是在堆内存中的同一个数据,所以当我们修改任意一个值的时候,修改的都是堆内存中的数据,而不是引用,所以只要修改了,同样引用的对象的值也自然而然的发生了改变。
其实,上面的例子就是一个简单的浅拷贝
单层数据的深拷贝
方法一
var obj1 = { a: 1, b: 1 }
var objString = JSON.stringify(obj1); //把json对象转换成json字符串
var obj2 = JSON.parse(objString); //json字符串转换成json对象
obj2.b = 4
console.log(obj1); // { a: 1, b: 1 }
console.log(obj2); // { a: 1, b: 4 }
但是使用JSON.stringify()
以及JSON.parse()
,是不可以拷贝undefined
、function
、RegExp
等等类型
方法二
var obj1 = {
a: 1,
b: 2,
}
var obj2 = Object.assign({}, obj1);
obj2.b = 5;
console.log(obj1);
console.log(obj2);
可以看到对于一层对象来说是没有任何问题的,但是如果对象的属性对应的是其它的引用类型的话,还是只拷贝了引用,修改的话还是会有问题。
深拷贝实现
function deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]))
}
// 判断如果当前的值是null的话;直接赋值为null
} else if (target === null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if (target.constructor === RegExp) {
result = target;
} else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
使用
var obj1 = {
a: 1,
b: 2,
c: {
name: 'aaron',
},
}
let obj2 = deepClone(obj1);
obj2.c.name = 'pipi';
console.log(obj1);
console.log(obj2);