JavaScript中内存使用规则--堆和栈

3,542 阅读5分钟

堆和栈都是运行时内存中分配的一个数据区,因此也被称为堆区和栈区,但二者存储的数据类型和处理速度不同。堆(heap)用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象;它是运行时动态分配内存的,因此存取速度较慢。栈(stack)中主要存放一些基本类型的变量和对象的引用,其优势是存取速度比堆要快,并且栈内的数据可以共享,但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

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。但逻辑全等运算与逻辑等于运算不同,它会对数据类型进行比较,看是否是引用的同一个数据。