概述
- 和大部分脚本语言(as、lua、python…)一样,javascript的内存分配和回收也是自动完成的,满足一定条件,就会被垃圾回收器自动回收
内存生命周期
- 内存分配:申明函数、对象时,系统会自动为其分配内存
- 内存使用:调用函数和对象时,读写内存
- 内存回收:满足条件,内存释放
变量在内存中的存储
- 堆内存:顺序随意,寄存速度比栈内存慢,由程序申请,操作简单,存储空间较大(取决于系统有效虚拟内存)
- 栈内存:先进后出,寄存速度快,栈数据可共享,由系统自动分配,数据固定不够灵活,空间大小有限制,超出则栈溢出(window下为1(?2)M)
- javascript中的基本数据类型(Undefined、Null、Boolean、Number 、String),分为变量标识和值,均保存在栈内存,变量标识指向其对应的值
- 引用数据类型(非基本数据类型如:Array、Object…)相对较为复杂,分为值、值所在堆地址以及变量标识,其中值保存在堆内存,变量标识和值所在堆地址保存在栈内存,变量标识指向其对应的值所在堆地址
1
2
3var a = "test";
var b = new String('test');
var c = [1,2,3];
其中a,b,c为变量标识,保存在栈内存;”test”为基本数据类型,也保存在栈内存,而new String('test')
(需特别注意,new出来的对象均为Object)和[1,2,3]
保存在堆内存,而标识他们的b,c之所以能找到它们,是因为生成这个对象的同时,栈内存中同时保存了值所在堆地址,b,c指向对应的值所在堆地址。
理解上述知识后,就能很好的解决javascript中关于传值和传引用的相关问题
1
2
3
4
5
6
7
8
9
10var a = 'test';
var b = {"key":"test"};
var c = a;
c = 'test1';
var d = b;
d['key'] = 'test1';
console.log(a);//输出:test
console.log(b);//输出:{"key":"test1"}
console.log(c);//输出:test1
console.log(d);//输出:{"key":"test1"}- 声明
a
并赋值"test"
,步骤:- 声明
a
,查找栈内存中是否存在a
,不存在则创建,不管是已存在还是新创建的a
,都指向undefined
- 为
a
赋值,在栈内存中查找"test"
,无则栈内存中创建"test"
,然后让a
指向"test"
- 声明
- 声明
b
并赋值{"key":"test"}
,步骤:- 声明
b
(同声明a
) - 为
b
赋值,堆内存中创建{"key":"test"}
,栈内存中创建其堆地址url_b
,让b
指向url_b
- 声明
- 声明
c
并赋值a
,步骤:- 声明
c
(同声明a
) - 为
c
赋值,在栈内存中查找a
,找不到则抛出错误,找到a
则让c
指向a
所指向的test
- 声明
- 为
c
赋值test1
,步骤:- 在栈内存中查找
c
,未找到则声明一个全局对象c
- 让
c
指向新的值"test1"
- 在栈内存中查找
- 声明
d
并赋值b
,步骤:- 声明
d
(同声明a
) - 为
d
赋值,在栈内存中查找b
,找不到则抛出错误,找到b
则让d
指向b
所指向的url_b
- 声明
d['key'] = 'test1'
,步骤:- 在栈内存中查找
d
,找不到则抛出错误,找到d
则通过其所指向url_b
找到堆内存中的{"key":"test"}
- 修改堆内存中的
{"key":"test"}
为{"key":"test1"}
- 在栈内存中查找
- 输出结果说明:
a
指向test
,输出test
b
指向url_b
,url_b
对应{"key":"test1"}
在堆内存中的值,输出{"key":"test1"}
c
指向test1
,输出test1
d
指向url_b
,url_b
对应{"key":"test1"}
在堆内存中的值,输出{"key":"test1"}
- 声明
内存回收条件(和as3.0等类似)
引用计数法:在内存中,没有其他人引用这个对象时,该对象被回收
适用场景
1
2
3
4var a = 'test';//"test"引用次数:1
var b = a;//"test"引用次数:2
a = 1;//"test"引用次数:1
b = 2;//"test"引用次数:0,满足被回收的条件局限:循环引用
1
2
3
4
5
6
7function foo(){
var c = {"key":"value"};
var d = [1,2,3];
c['key'] = d;
d[0] = c;
}
foo();foo
函数执行完毕,{"key":"value"}
和[1,2,3]
被关在foo的作用域内,外面已经没有人可以使用它们,按理应该没有存在的价值,会被删除,然而因为它们之间循环引用,引用计数始终不为0,因而不满足引用计数法的内存回收条件,这种情况下,会使用另外一种算法
- 标记清除法:在内存中,访问不到的对象可被回收
- 这种算法要比引用计数法覆盖范围更大,命题
引用计数为0则该对象无法访问
成立,其逆命题不成立 - 对象进入作用域,标记为1,出了其作用域,标记成0,javascript会隔一段时间进行扫描,发现有标记为0的对象,则回收。
- 上述循环引用的例子中,执行完毕
foo
后堆内存中的{"key":"value"}
和[1,2,3]
,栈内存中的c
,d
,以及各自对应的堆地址,均被标记为0,可回收。
- 这种算法要比引用计数法覆盖范围更大,命题