動態(tài)變量存在于堆(heap)中,局部變量和函數(shù)參數(shù)存在于棧(stack)中。
調(diào)用 new,就分配到 heap,否則就分配在 stack。
heap 自高向低分配地址,stack 自低向高分配地址。
Javascript中,const arr = []
以及
const obj = {}
等,背后也已經(jīng)隱含了 new 的操作,即 new Array()
和 new Object()
由一段 Javascript 代碼說明在執(zhí)行代碼的時候,堆棧都是如何去分配的。首先定義一個類:
class Point {
constructor( x, y ) {
this.px = x
this.py = y
}
move( dx, dy ) {
this.px += dx
this.py += dy
}
}
假設(shè)只定義了一個 Point
類而不使用,那就沒有什么意義,所以需要一個函數(shù)去 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,在實例化的過程中,執(zhí)行了構(gòu)造函數(shù) constructor,在 heap 中分配了一個空間給 px,由于 px 存儲了一個整形,占用了 4個字節(jié),所以就會看到地址以 4 遞進,同理 py。然后將 p1 指向了這個實例的起始地址 1000。
二
const p2 = new Point( 5, 6 )
這里的解釋跟上面的解釋同樣道理。
三
p1.move( 3, 4 )
此處調(diào)用了一個 move 函數(shù),這個函數(shù)是 p1 上的一個方法,于是 run 函數(shù)被掛起保存,暫停執(zhí)行,也就是所謂的棧幀。開始執(zhí)行 p1.move函數(shù),stack 中開始插入該函數(shù)相關(guān)的局部變量和參數(shù)。
在執(zhí)行 p1.move函數(shù) 的過程中,move 使用到了 this,所以計算機需要知道這個 this 是什么。由于 move 函數(shù)是由 p1調(diào)用,所以這個 this 指向了這個實例本身,也就是 p1,所以 this 中的內(nèi)存地址為 p1 的地址,指向
heap 中同一個地址,即 1000。
接下來,對于 this.px += dx
,就很好理解了。因為前面已經(jīng)得到了 this 的具體,所以就知道是 1000 地址中的 px 執(zhí)行 += dx,同理 this.py += dy
。于是,就變成了如下圖:
四
五
p1.move函數(shù) 執(zhí)行完畢之后,相關(guān)的變量就從 stack 中彈出,當然此時 run 函數(shù)也執(zhí)行完畢,相關(guān)變量也彈出,此時 stack 已經(jīng)空了。當 heap 中的變量,或者說對象不再有引用的時候,GC 就將其回收,heap 也會空了。
延伸1
假設(shè)改寫 run函數(shù):
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 中,對象屬于符合類型,也叫引用類型。所以執(zhí)行類似 const p3 = p2
的時候,得到的并不是一個和 p2 一樣的新的對象,而是得到一個引用,也就是一個地址,跟 p2 一樣的一個地址,即 p3 => 100C
,指向了同一個對象。
所以在執(zhí)行
p3.move( 1, 1 )
的時候,也會改變 p2 指向的對象,即 p2 和 p3 綁在了一起,雙劍合一。
延伸2
增加一個 Line 類:
class Line {
constructor( sPoint, ePoint ) {
this.start = sPoint
this.end = ePoint
}
}
假設(shè)改寫 run函數(shù):
function run() {
const p1 = new Point( 1, 2 )
const p2 = new Point( 5, 6 )
const line = new Line( p1, p2 )
}
run()
現(xiàn)在要做的就是兩點(Point)連成一個直線(Line),且看內(nèi)存中是如何工作:
new Line( p1, p2 )
的過程中,傳入的 p1 和 p2 其實只是傳入了這兩個存儲的地址,所以 Line 內(nèi)部的構(gòu)造函數(shù) constructor 拿到的也就是兩個地址,即:
this.start = sPoint => p1 => 1000
this.end = ePoint => p2 => 100C