動態(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()
當(dāng)程序運行到了 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 中彈出,當(dāng)然此時 run 函數(shù)也執(zhí)行完畢璧眠,相關(guān)變量也彈出缩焦,此時 stack 已經(jīng)空了。當(dā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