关于堆和栈
1. 关于堆和栈的定义
栈(stack)栈会自动分配内存空间,会自动释放,存放基本类型简单的数据段,占据固定的大小空间。
堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际保存的不是变量本身,而是指向该对象的指针。
2. 它们之间的区别
- 栈:所有的方法都存放在方法的变量都是存放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然的销毁。
栈的优点:存取的速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享。栈的缺点:存在栈中的数据大小与生存期必须是确定性的,缺乏灵活性。
- 堆:堆内存中的对象不会随着方法的结束而销毁,即使方法结束后,这个对象还可能被另一个变量所引用(参数传递)。创建对象是为了反复利用的,这个对象将被保存到运行时的数据区。
3. 堆和栈的溢出,
- 栈:可以递归调用方法,这样随着栈深度的增加,JVM维持着一条长长的方法调用轨迹,直到内存不够分配,产生栈溢出。
- 堆:循环创建对象,通俗一点就是不断的new一个对象。
下面看看传值与传址的区别:
其实这两者区别就是基本类型和引用类型的例子,看一下下面的例子:
- 关于数组的例子
let a = [1,2,3,4]
let b = a
let c = b[0]
console.log(c) //1
b[0] = 5
c = 2
console.log(a) //[5,2,3,4]
console.log(b) //[5,2,3,4]
console.log(c) //2
我们可以看出,b只是引用了a对象的地址,修改b的话a也会随着去修改,因为它们都指向堆内同一地址的对象,但c只是将b[0]也就是数组b里面的第一个值取出并赋值给c,相当于复制一个相等的值去栈内存中开辟一个空间并存储到里面中。
- 关于对象的例子
let obj = {a:1,b:2,c:3}
let obj2 = obj
let d = obj2.a
console.log(d); // 1
obj2.a = 4
console.log(d); // 1
d = 5
console.log(obj); // { a: 4, b: 2, c: 3 }
console.log(obj2); // { a: 4, b: 2, c: 3 }
console.log(d); // 5
可以看出,对象和数组的例子上基本一样,也是定义了一个obj并赋给了它一个对象类型的值,obj2 = obj 也知识引用了这个我们这个对象的地址,d这个变量我们赋给它了一个对象中的a的值,那么这个b也是相当于复制了一个obj2中a的值然后在栈内存中开辟了一块空间并也是存储到了里面。
4. 深浅拷贝
浅拷贝:就是指只复制了第一层属性,复制对象是基本类型。在复制基本类型时,直接使用等号完成复制,在复制引用类型时,要循环便利每个对象,对每一个属性或值使用等号完成复制。
举一个小例子:
//复制基本类型数据
let a = 1;
let b = a
console.log(b) // 1
b = 2
console.log(b) // 2
//复制引用类型数据
let a = ["1","2","3","4"]
let b = []
for(let i = 0; i
可以看到,经过循环的复制a中的属性并添加到b中,a里面的值已经完全是复制给b了,而不是改变它指向的地址,也就是说b在堆内存中已经开辟了属于b的空间,复制了a中的所有值并添加到b这个已经开辟了的空间中,所以并不是改变了它的指向。
那么如果数组里面的值是一个对象或者是还是一个引用类型的值怎么办?(也就是说引用类型的某个属性还是引用属性) 举个例子:
let obj = {
a:1,
b:"2",
c:{
d:3,
e:"4"
}
}
在上面的这个例子中,obj还是一个引用类型的数据,是一个对象。
下面该说我们的拷贝,也就是深拷贝了。
深拷贝:对属性中所有引用类型的值再进行遍历,直到它变成基本类型的值为止,利用递归来实现深拷贝。
话不多说,还是去举一个例子:
let obj = {
a: 1,
b: "2",
c: {
d: 3,
e: "4"
},
f:["5","6","7"]
}
function copyObject(obj) {
let newobj = {}
if (typeof (obj) == "object" && obj !== null) {
newobj = obj instanceof Array ? [] : {}
for(let i in obj){
newobj[i] = copyObject(obj[i])
}
} else{
newobj = obj
}
return newobj
}
let newobj = copyObject(obj)
console.log(newobj); //{ a: 1, b: '2', c: { d: 3, e: '4' }, f: [ '5', '6', '7' ] }
console.log(obj); //{ a: 1, b: '2', c: { d: 3, e: '4' }, f: [ '5', '6', '7' ] }
newobj.a = 5
console.log(newobj); //{ a: 5, b: '2', c: { d: 3, e: '4' }, f: [ '5', '6', '7' ] }
console.log(obj); //{ a: 1, b: '2', c: { d: 3, e: '4' }, f: [ '5', '6', '7' ] }
从上面这个例子中可以看出,我们在倒数第三行代码中重新的给拷贝出来的新对象的a属性赋值为5,再去打印新对象与原对象,发现只有新对象的a属性变为了5,而原对象的a属性是没有被改变的,也就说明了我们确确实实利用递归进行了引用数据类型的深拷贝,这也说明了新对象是全新的一个数据,它们的地址也不是指向的同一个地址,这样一个利用递归实现引用数据类型的深拷贝就完成啦!