前言
像C语言这样的底层语言一般都有底层的内存管理接口,像malloc()
和free()
。像JavaScript这样的语言在创建变量的时候自动完成了内存的分配,并且在不需要的时候自动释放。释放的过程称之为“垃圾回收”。这个“自动”就是混乱的根源,它让我们以为使用JavaScript code就不用关心内存管理。
JavaScript的内存空间
我们coding的时候应该都想过一个问题,我们定义的变量都存储在何处?我们知道程序在运行的时候会创建进程,创建进程会分配资源。这里的资源就包括内存。
一说到内存,就会想到栈内存和堆内存,对于栈内存和堆内存的理解可以看数据结构的堆、栈和操作系统的堆内存、栈内存的区别
局部的、占用空间确定的数据一般都放在stack中,反之就放在heap中。对应的JavaScript的变量,基本类型:string、number、boolean、null(不要在意它的type是object)、undefined、symbol存储在栈区;复杂数据类型(引用类型):object,像array、function在JavaScript中也都是object,一般都是存储在堆区。
在区分了栈区和堆区之后,我们就能更轻易的理解基本类型的数据和引用类型的数据的区别。
举个例子:
1 | var a1 = 0; // 变量对象 |
基本数据类型和复杂类型(引用类型)的区别
变量对象的在执行上下文栈中的状态
变量对象中的基本数据类型的数据都是直接以栈的形式存储,而引用类型的数据本体存储在堆区,栈区只存储地址的引用(或者说指针)。这也就很容易解释JS中一个常见现象。关于执行上下文的内容可以看JS进阶系列-第四篇-执行上下文
1 | // demo02.js |
再来一个复杂点的,稍微注意下连等号和两个赋值操作的区别,得到引用在赋值操作之前,得到引用从左到右,赋值操作从右到左
1 | var a = {n : 1}; |
小结:执行上下文栈以栈的形式组织和管理,每个执行上下文都有自己的变量对象,在变量对象的中基本类型直接存储,引用类型存储在堆中。所以可以说基本类型存储在栈中,引用类型存储在堆中。注意同一个变量对象中的数据是在栈的统一层级,不要对“基本类型数据存储在栈中”这句话产生误解。
内存管理
前面说到JavaScript的内存是自动的,内存的生命周期一般为:
1.分配你所需要的内存,
2.使用你分配的内存(读、写),
3.释放内存
这里的自动主要包括自动分配和自动回收(释放),所以在使用JavaScirpt coding时只会有看到“使用内存”。这也是忽略内存管理的主要原因。
自动分配
1 | var n = 123; // 给数值变量分配内存 |
自动回收
JavaScript的自动回收指在内存不再需要的时候释放,如何确定内存不再需要却是一个难题。所以自动回收是一个不断查找内存是否还被需要的机制。自动回收不是全能的,它是一个近似的过程,它不能确定某个内存一定不被需要了,所以只能解决一般问题。
垃圾回收机制隔一段时间检查一下那些不被需要了就将其释放。
这里主要介绍一下垃圾回收的算法,也就是检查变量不被需要的算法。
引用计数垃圾收集
最初级的垃圾回收算法,此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
示例:
1 | var o = { |
限制:无法处理循环引用
标记清除法
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
对于JavaScript来说,在局部作用域中,函数调用完毕,局部变量就没有必要存在了。
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。
参考链接
内存空间详解
内存管理
关于js中 “栈空间的先进后出,后进先出” 的疑问?