为了账号安全,请及时绑定邮箱和手机立即绑定

Javascript中的堆和栈(《JS高级程序设计》阅读笔记)

标签:
JavaScript

堆和栈都是运行时内存中分配的一个数据区,因此也被称为堆区和栈区,但二者存储的数据类型和处理速度不同。堆(heap)用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象(引用类型的值通常大小不固定,所以被存储在堆内存中);它是运行时动态分配内存的,因此存取速度较慢。栈(stack)中主要存放一些基本类型(Undefined、Null、Boolean、Number 和 String)的变量和对象的引用(基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中),其优势是存取速度比堆要快,并且栈内的数据可以共享,但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
1.栈的使用规则
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。例如下面的代码定义两个变量,变量的值都是数字类型。

var a=3;
var b=3;

JavaScript解析器先处理 var a=3;,首先会在栈中创建一个变量为a引用,然后查找栈中是否有3这个值,如果没有找到,就将3存放进来,然后将a指向3。接着处理 var b=3;,在创建为b的引用变量后,查找栈中是否有3这个值,因为此时栈中已经存在了3,便将b直接指向3。这样,就出现了a与b同时指向3的情况。此时,如果再令a=4,那么JavaScript解释引擎会重新搜查栈中是否有4这个值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
2.堆的使用规则
下面通过Array来看一下堆的行为,例如存在下面的代码:

var fruit_1="apple";
var fruit_2="orange";
var fruit_3="banana";
var oArray=[fruit_1,fruit_2,fruit_3];
var newArray=oArray;

当创建数组时,就会在堆内存创建一个数组对象,并且在栈内存中创建一个对数组的引用(即引用类型在栈内存中存放的只是该对象的访问地址,而在堆内存中为这个值分配空间存储)。变量fruit_1、fruit_2、fruit_3为基本数据类型,它们的值直接存放在栈中;newArray、oArray为复合数据类型(引用类型),他们的引用变量存放在栈中,指向于存放在堆中的实际对象。
注意,newArray的值等于变量引用oArray,所以它也是复合数据类型(引用类型)。此时,如果更改变量fruit_1、fruit_2、fruit_3的值,那么其实是更改栈中的值;如果更改newArray或oArray的值,那么其实是更该堆中的实际对象,因此,对两个变量引用都会发生作用(因为从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象)。例如,首先更改newArray的值,然后看oArray的值,代码如下:

alert(oArray[1]);// 返回 orange
newArray[1]="berry";
alert(oArray[1]);// 返回 berry

同样,首现更改oArray的值,然后看newArray的值,代码如下:

alert(newArray[1]);// 返回 orange
oArray[1]='tomato';
alert(newArray[1]);// 返回 tomato

JavaScript堆不需要程序代码来显示地释放,因为堆是由自动的垃圾回收来负责的,每种浏览器中的JavaScript解释引擎有不同的自动回收方式,但一个最基本的原则是:如果栈中不存在对堆中某个对象的引用,那么就认为该对象已经不再需要,在垃圾回收时就会清除该对象占用的内存空间。因此,在不需要时应该将对对象的引用释放掉(解除引用),以利于垃圾回收,这样就可以提高程序的性能。释放对对象的引用最常用的方法就是为其赋值为null,这种做法适用于大多数全局变量和全局对象的属性。局部变量会在他们离开执行环境时自动被解除引用, 例如下面代码将newArray赋值为null:

newArray=null;

注意:解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
3.易犯的错误
在堆和栈的使用问题上,最易犯的错误就是String的使用,例如下面的代码:

var str=new String('abc');
var str='abc';

同样是创建两个字符串,第一种是用new关键字来新建String对象,对象会存放在堆中,每调用一次就会创建一个新的对象;而第二种是在栈中,栈中存放值‘abc’和对值的引用。推荐使用第二种方式创建多个'abc'字符串,这种写法在内存中只存在一个值,有利于节省内存空间。同时它可以在一定程度上提高程序的运行速度,因为存储在栈中,其值可以共享,并且由于栈访问更快,所以对于性能的提高大有裨益。而第一种方式每次都在堆中创建一个新的String对象,而不管其字符串值是否相等及是否有必要创建新对象,从而加重了程序的负担。并且堆的访问速度慢,对程序性能的影响也大。另外,出于逻辑运算的考虑,当对两个变量进行比较时,使用堆和栈存储就会有差异。下面来看一下逻辑等于和逻辑全等运算,深入理解一下堆和栈:
(1) 例如下面的代码,实际只比较栈中的值:

var str1='abc';
var str2='abc';
alert(str1==str2); // true
alert(str1===str2); // true

不管是逻辑等于和逻辑全等运算都返回true,可以看出str1和str2指向同一个值。
(2)例如下面的代码,实际只比较堆中的值:

var str1=new String('abc');
var str2=new String('abc');
alert(str1==str2); // false
alert(str1===str2); // false

不管是逻辑等于还是逻辑全等都返回false,可以看出str1和str2指向的不是同一个对象。
(3)例如下面的代码,比较堆和栈中的值:

var str1=new String('abc');
var str2='abc';
alert(str1==str2); // true
alert(str1===str2); // false

在进行逻辑等于和逻辑全等运算时,会首先将变量转成相同的数据类型,然后进行对比。变量str1和str2的数据类型虽然不同,但比较运算还是返回true。但逻辑全等运算与逻辑等于运算不同,它会对数据类型进行比较,看是否是引用的同一个数据。
参考
JavaScript中内存使用规则--堆和栈

点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消