以前写了一篇文章浏览器是怎么看闭包的,发现有些读者对js内存分配与回收懵懵懂懂,理解文章的配图有些困难,我想主要是由于配图省略了一些细节。今天专门写一篇关于js内存分配回收的文章,帮助你们理解js代码的内存表示。原文备份在这里javascript
先唠叨些基本知识:html
咱们都知道的是,javascript中值类型是在变量所在的内存单元中存放的,而对于引用类型的对象,变量所在的内存单元存放的是堆空间中对象的内存地址。咱们还应该知道的是,函数在执行时,局部变量是在栈空间中建立,引用对象是在堆空间中建立的。java
咱们仍是从代码入手:浏览器
var a = 'abc'
var b = 123
var c = true
var d = undefined
var e = null
var f = {
n: 'test'
}复制代码
这段代码咱们定义了六个全局变量,每一个变量赋予不一样类型的值,咱们发现,a、b、c、d、e基本类型的值占据一个内存单元,而变量f内存储的是堆中对象的地址。以下图表示:闭包
变量f中存储的0x00012345是堆中对象的内存地址。函数
一切都很容易理解。细心的同窗也许会指出,null也是对象,经过typeof null 表达式获得的结果是'object'。关于这个,我想说的是typeof null = 'object' 这个现象是历史遗留bug。事实上null是空值,并非对象。post
js的类型值有1-3位是表示类型,其它位表示真实值。
000: object. The data is a reference to an object.
也就是说,000开头的被认为是指向对象引用。因为js中的null是空指针,在大多数平台上空指针的前两位是0x00,再加上null的数值表示是0,因此null的前三位是0x000,js引擎会认为它是指向对象的引用,这是一个历史遗留bug。但事实上,null是空值。详细解释参见这里。测试
说到null,咱们还要用图形表示一下null所起到的做用。对于上面的代码,咱们将引用类型f置为null,该变量将再也不指向堆中对象。图形表示以下:spa
你会发现,本来f指向堆对象的线消失了,堆中对应的对象再也不被f引用。3d
看到这里,你也许会问:咦,那没有任何对象指向那个堆对象了,它还占据内存吗?若是还占据的话,岂不是占着茅坑不那啥吗?我想,若是能想到这一点,说明你是一个有追求的js开发者。
是的,本来堆空间中的那个对象确实没有引用了,js引擎会在下一个垃圾回收节点将它回收掉。
为了帮助你们更好的理解内存的分配与释放,建议你们在看配图的时候,必定要谨记箭头的走向,认真看箭头从哪一个对象出发,又是指向哪一个对象的。由于箭头指向表明变量引用,而引用是垃圾回收器辨别内存垃圾的依据。什么是垃圾呢?按照垃圾回收器的理解是,从根对象触发,沿着箭头指向,可以找到的对象,都不该该断定为垃圾。相反,从根对象触发,沿着箭头指向,不可以找到的对象,被断定为垃圾,将在下一个垃圾回收节点回收掉。
那么,与之相伴的是内存泄漏,什么叫内存泄漏呢?通俗一点讲,就是某个对象已经不会再被咱们用到,可是垃圾回收器却发现从根对象仍然可以找到它,因此不认为它是垃圾,所以不会回收它,可是它确实对咱们没有用处。这样就形成了内存的浪费,这种现象称做内存泄漏。
理解了断定内存垃圾的方式以及内存泄漏,咱们就能够经过画图的方式来检验代码是否存在内存泄漏,代码是否健壮。
上面代码中,f指向的对象是一个简单对象,只包含一个属性,若是堆中的是一个复杂对象,又该如何表示呢?让咱们继续看代码
var a = {
b: 123,
c: 'abc',
d: true,
e: null,
f: {
h: 'test',
j: {
k: 567
}
}
}复制代码
咱们定义了一个全局变量a,指向堆内存中的一个复杂对象。以下图:
全局定义的变量是常驻内存的,为何常驻内存?咱们从垃圾回收的角度分析一下:
因此,全局定义的变量a所关联的对象是常驻内存的。
再次思考一下,咱们如何让垃圾回收器回收堆空间右侧的那两个对象呢?聪明的你也许想到了
a.f = null复制代码
是的,将a.f的指针置为null就能够了。咱们从垃圾回收的角度分析一下,a.f = null这段代码执行之后,f变量中存储的值变成了null,再也不指向右侧的两个对象,按照咱们以前的方法,从根对象window开始,沿着箭头寻找,找不到右侧对象,因此右侧两个对象成为内存垃圾,将被GC(垃圾回收器)回收掉。这就是当咱们为一个变量赋值null以后,变量在内存中的变化。以下图所示:
固然,咱们也能够为变量赋予其余基础类型的值,断开和堆中对象的联系。
对于再复杂的对象,你们能够触类旁通。接下来,咱们看一下函数的定义:
function say() {
var a = '测试'
var b = {
c: 123
}
}复制代码
能够看到函数对象指针在全局变量区,函数自己在堆中存放,函数我只画了了几个常见的属性。细心的你也许发现有个[[Socpes]]的属性,之后讲闭包的时候再对它做详细介绍,这里只大概介绍一下。
[[Scopes]]属性是在函数建立的时候附加的属性,表明该函数的做用域链。
继续看一段代码:
function say() {
var a = '测试'
var b = {
c: 123
}
}
say()复制代码
很简单,咱们定义一个函数,并执行它,变量的内存图以下:
函数运行时,局部变量分配在栈空间,此时,外部window对象与栈空间中的变量没有引用关系。局部变量a是值类型,在栈中存放,局部变量b是引用类型,栈中存放对象在堆中的内存地址。
函数运行结束后的图示以下:
函数运行结束,局部变量因为没有外部引用,因此所有释放,同时堆中的对象也失去了引用,成为内存垃圾,被GC回收掉。
至此,关于js代码的内存表示就先告一段落,经过画图的方式,但愿你们能对程序的执行有感性的理解,也但愿能帮助你们经过画图的方式去断定内存垃圾。另外,你们在看下一篇文章浏览器是怎么看闭包的的时候,会发现有一些细节没有表示,你们不要太过于纠结,只需注意箭头走向便可。