动态变量存在于堆(heap)中,局部变量和函数参数存在于栈(stack)中。
调用 new,就分配到 heap,否则就分配在 stack。
heap 自高向低分配地址,stack 自低向高分配地址。
Javascript中,const arr = []
以及
const obj = {}
等,背后也已经隐含了 new 的操作,即 new Array()
和 new Object()
由一段 Javascript 代码说明在执行代码的时候,堆栈都是如何去分配的。首先定义一个类:
class Point {
constructor( x, y ) {
this.px = x
this.py = y
}
move( dx, dy ) {
this.px += dx
this.py += dy
}
}
假设只定义了一个 Point
类而不使用,那就没有什么意义,所以需要一个函数去 new 这个类进行实例化,从而使用这个类和实例。
function run() {
const p1 = new Point( 1, 2 )
const p2 = new Point( 5, 6 )
p1.move( 3, 4 )
}
run()
当程序运行到了 run()
的时候,这篇文章的重点就开始了:
一
const p1 = new Point( 1, 2 )
const 声明了一个 p1 变量,在 stack 中分配了一个空间。接着又 new 了一个 Point 类,得到一个实例,存在于 heap 的某个地方,这个地方的起始地址为十六进制的 1000,在实例化的过程中,执行了构造函数 constructor,在 heap 中分配了一个空间给 px,由于 px 存储了一个整形,占用了 4个字节,所以就会看到地址以 4 递进,同理 py。然后将 p1 指向了这个实例的起始地址 1000。
二
const p2 = new Point( 5, 6 )
这里的解释跟上面的解释同样道理。
三
p1.move( 3, 4 )
此处调用了一个 move 函数,这个函数是 p1 上的一个方法,于是 run 函数被挂起保存,暂停执行,也就是所谓的栈帧。开始执行 p1.move函数,stack 中开始插入该函数相关的局部变量和参数。
在执行 p1.move函数 的过程中,move 使用到了 this,所以计算机需要知道这个 this 是什么。由于 move 函数是由 p1调用,所以这个 this 指向了这个实例本身,也就是 p1,所以 this 中的内存地址为 p1 的地址,指向
heap 中同一个地址,即 1000。
接下来,对于 this.px += dx
,就很好理解了。因为前面已经得到了 this 的具体,所以就知道是 1000 地址中的 px 执行 += dx,同理 this.py += dy
。于是,就变成了如下图:
四
五
p1.move函数 执行完毕之后,相关的变量就从 stack 中弹出,当然此时 run 函数也执行完毕,相关变量也弹出,此时 stack 已经空了。当 heap 中的变量,或者说对象不再有引用的时候,GC 就将其回收,heap 也会空了。
延伸1
假设改写 run函数:
function run() {
const p1 = new Point( 1, 2 )
const p2 = new Point( 5, 6 )
const p3 = p2
p1.move( 3, 4 )
p3.move( 1, 1 )
}
run()
由于在 Javascript 中,对象属于符合类型,也叫引用类型。所以执行类似 const p3 = p2
的时候,得到的并不是一个和 p2 一样的新的对象,而是得到一个引用,也就是一个地址,跟 p2 一样的一个地址,即 p3 => 100C
,指向了同一个对象。
所以在执行
p3.move( 1, 1 )
的时候,也会改变 p2 指向的对象,即 p2 和 p3 绑在了一起,双剑合一。
延伸2
增加一个 Line 类:
class Line {
constructor( sPoint, ePoint ) {
this.start = sPoint
this.end = ePoint
}
}
假设改写 run函数:
function run() {
const p1 = new Point( 1, 2 )
const p2 = new Point( 5, 6 )
const line = new Line( p1, p2 )
}
run()
现在要做的就是两点(Point)连成一个直线(Line),且看内存中是如何工作:
new Line( p1, p2 )
的过程中,传入的 p1 和 p2 其实只是传入了这两个存储的地址,所以 Line 内部的构造函数 constructor 拿到的也就是两个地址,即:
this.start = sPoint => p1 => 1000
this.end = ePoint => p2 => 100C