JavaScript內(nèi)存機(jī)制之堆內(nèi)存(Heap)與棧內(nèi)存(Stack)詳解
在 JavaScript 引擎(如 V8)中,內(nèi)存主要分為兩個(gè)區(qū)域:棧內(nèi)存(Stack) 和 堆內(nèi)存(Heap)。
它們就像公司的辦公桌和倉(cāng)庫(kù),分工明確,協(xié)作高效。
1. 核心比喻:辦公桌 vs 倉(cāng)庫(kù)
為了通俗易懂,我們把瀏覽器內(nèi)存想象成一家公司:
棧(Stack) = 員工的辦公桌
- 特點(diǎn):空間小,但就在手邊,存取速度極快。
- 用途:存放正在處理的臨時(shí)數(shù)據(jù)(如函數(shù)調(diào)用、局部變量)。
- 管理:老板(JS 引擎)嚴(yán)格管理。你離開(kāi)座位(函數(shù)執(zhí)行結(jié)束),桌子上的東西立刻被清空。
- 結(jié)構(gòu):先進(jìn)后出(LIFO),像疊盤(pán)子一樣。
堆(Heap) = 公司的公共倉(cāng)庫(kù)
- 特點(diǎn):空間巨大,但距離遠(yuǎn),存取速度相對(duì)較慢。
- 用途:存放大件物品、復(fù)雜對(duì)象(如大數(shù)組、復(fù)雜對(duì)象、閉包)。
- 管理:比較松散。東西扔進(jìn)去就不管了,直到倉(cāng)庫(kù)滿(mǎn)了,清潔工(垃圾回收器 GC)才會(huì)來(lái)清理沒(méi)人用的東西。
- 結(jié)構(gòu):無(wú)序,像一個(gè)巨大的雜物間,需要通過(guò)“地址標(biāo)簽”才能找到東西。
2. 棧(Stack):快速、有序、自動(dòng)清理
存儲(chǔ)內(nèi)容
- 基本數(shù)據(jù)類(lèi)型:
Number,String,Boolean,Null,Undefined,Symbol,BigInt。 - 執(zhí)行上下文:函數(shù)調(diào)用棧(Call Stack),記錄當(dāng)前執(zhí)行到哪里了。
工作機(jī)制
- 分配:當(dāng)聲明一個(gè)變量或調(diào)用一個(gè)函數(shù)時(shí),內(nèi)存會(huì)自動(dòng)在棧頂分配一塊固定大小的空間。
- 釋放:當(dāng)函數(shù)執(zhí)行完畢或變量超出作用域時(shí),這塊內(nèi)存會(huì)自動(dòng)彈出并釋放。
- 速度:極快,因?yàn)椴恍枰檎遥苯硬僮鳁m斨羔槨?/li>
注意:在 JS 中,短字符串有時(shí)也會(huì)存儲(chǔ)在棧中(取決于引擎優(yōu)化),但邏輯上我們將其視為值類(lèi)型。
3. 堆(Heap):龐大、無(wú)序、手動(dòng)回收
存儲(chǔ)內(nèi)容
- 引用數(shù)據(jù)類(lèi)型:
Object,Array,Function,Date,RegExp等。 - 大型數(shù)據(jù):無(wú)論數(shù)據(jù)多大,都存放在堆中。
工作機(jī)制
- 分配:當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),引擎會(huì)在堆中開(kāi)辟一塊空間存放數(shù)據(jù)。
- 引用:棧中只存儲(chǔ)一個(gè)指針(內(nèi)存地址),指向堆中的這塊數(shù)據(jù)。
- 釋放:不會(huì)自動(dòng)立即釋放。需要依靠瀏覽器的垃圾回收機(jī)制(Garbage Collection, GC) 來(lái)定期掃描,找出不再被引用的對(duì)象并清除。
4. 代碼實(shí)戰(zhàn):數(shù)據(jù)是如何存儲(chǔ)的?
讓我們通過(guò)代碼看看棧和堆是如何協(xié)作的。
場(chǎng)景一:基本類(lèi)型(全在棧中)
let a = 10; let b = a; // 拷貝值 b = 20; console.log(a); // 10 console.log(b); // 20
內(nèi)存圖解:
Stack (棧) +-------+ | b: 20 | <-- 修改 b,不影響 a +-------+ | a: 10 | <-- a 保持原值 +-------+
結(jié)論:基本類(lèi)型是值拷貝?;ゲ挥绊?。
場(chǎng)景二:引用類(lèi)型(棧存地址,堆存數(shù)據(jù))
let obj1 = { name: "Lingma" };
let obj2 = obj1; // 拷貝地址(指針)
obj2.name = "Aliyun";
console.log(obj1.name); // "Aliyun" ?? obj1 也被改變了!
console.log(obj2.name); // "Aliyun"
內(nèi)存圖解:
Stack (棧) Heap (堆)
+----------+ +------------------+
| obj1 |------------->| { name: "Aliyun" } |
+----------+ +------------------+
| obj2 |-------------^ (同一個(gè)對(duì)象)
+----------+
結(jié)論:引用類(lèi)型是引用拷貝(淺拷貝)。
obj1和obj2指向堆中的同一個(gè)對(duì)象。修改其中一個(gè),另一個(gè)也會(huì)變。
5. 垃圾回收:誰(shuí)在打掃戰(zhàn)場(chǎng)?
既然堆內(nèi)存不會(huì)自動(dòng)釋放,那什么時(shí)候清理呢?
瀏覽器使用 垃圾回收機(jī)制(GC)。主流算法是 標(biāo)記-清除(Mark-and-Sweep)。
工作流程
- 標(biāo)記:GC 從根節(jié)點(diǎn)(如
window、全局變量)出發(fā),遍歷所有能訪(fǎng)問(wèn)到的對(duì)象,打上“存活”標(biāo)記。 - 清除:遍歷堆內(nèi)存,那些沒(méi)有被打上標(biāo)記的對(duì)象,說(shuō)明已經(jīng)沒(méi)有任何變量引用它們了,于是被判定為“垃圾”,占用內(nèi)存被釋放。
內(nèi)存泄漏(Memory Leak)
如果代碼中存在意外的全局變量、未清理的定時(shí)器或循環(huán)引用,導(dǎo)致某些對(duì)象永遠(yuǎn)無(wú)法被 GC 標(biāo)記為“可回收”,內(nèi)存就會(huì)越占越多,最終導(dǎo)致頁(yè)面卡頓甚至崩潰。
常見(jiàn)泄漏場(chǎng)景:
// 1. 意外全局變量
function leak() {
leakedVar = "I am global now"; // 忘記寫(xiě) let/var/const
}
// 2. 未清除的定時(shí)器
const timer = setInterval(() => {
console.log("I never stop");
}, 1000);
// 如果不清除 clearInterval(timer),回調(diào)函數(shù)及其引用的變量永遠(yuǎn)不會(huì)被回收
6. 總結(jié)與面試考點(diǎn)
| 特性 | 棧(Stack) | 堆(Heap) |
|---|---|---|
| 存儲(chǔ)內(nèi)容 | 基本類(lèi)型、執(zhí)行上下文 | 引用類(lèi)型(對(duì)象、數(shù)組等) |
| 大小 | 小,固定大小 | 大,動(dòng)態(tài)分配 |
| 存取速度 | 快 | 慢 |
| 管理方式 | 自動(dòng)分配與釋放(LIFO) | 垃圾回收機(jī)制(GC) |
| 數(shù)據(jù)結(jié)構(gòu) | 線(xiàn)性,有序 | 非線(xiàn)性,無(wú)序 |
| 拷貝行為 | 值拷貝(深拷貝效果) | 引用拷貝(淺拷貝) |
面試高頻問(wèn)答
Q1: 為什么基本類(lèi)型賦值互不影響,而對(duì)象賦值會(huì)相互影響?
A: 因?yàn)榛绢?lèi)型存在棧中,賦值是復(fù)制值;對(duì)象存在堆中,棧里只存地址,賦值是復(fù)制地址,兩者指向同一塊堆內(nèi)存。
Q2: 什么是深拷貝?如何實(shí)現(xiàn)?
A: 深拷貝是在堆中開(kāi)辟一塊新內(nèi)存,將原對(duì)象的所有層級(jí)數(shù)據(jù)完整復(fù)制一份。實(shí)現(xiàn)方式:JSON.parse(JSON.stringify(obj))(有局限)、遞歸復(fù)制、或使用 structuredClone API。
Q3: 閉包會(huì)導(dǎo)致內(nèi)存泄漏嗎?
A: 不一定。閉包會(huì)阻止外部變量被回收,這是正?,F(xiàn)象。但如果閉包長(zhǎng)期存在且引用了巨大的無(wú)用數(shù)據(jù),且無(wú)法被 GC 回收,才會(huì)導(dǎo)致泄漏。
博主寄語(yǔ):
理解堆和棧,不僅僅是為了應(yīng)付面試。
當(dāng)你遇到“修改了一個(gè)對(duì)象,另一個(gè)莫名其妙也變了”的 Bug 時(shí),你會(huì)想起堆內(nèi)存的共享特性。
當(dāng)你發(fā)現(xiàn)頁(yè)面越來(lái)越卡時(shí),你會(huì)想起堆內(nèi)存的垃圾回收。記住口訣:
棧小快,存基本,自動(dòng)清理不費(fèi)力。
堆大慢,存對(duì)象,引用拷貝要注意。
垃圾回收靠標(biāo)記,內(nèi)存泄漏要警惕。
以上就是JavaScript內(nèi)存之堆內(nèi)存(Heap)與棧內(nèi)存(Stack)詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript堆內(nèi)存(Heap)與棧內(nèi)存(Stack)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ES6中new Function()語(yǔ)法及應(yīng)用實(shí)例分析
這篇文章主要介紹了ES6中new Function()語(yǔ)法及應(yīng)用,結(jié)合實(shí)例形式分析了ES6中new Function()的基本語(yǔ)法、功能、原理、閉包等相關(guān)使用技巧,需要的朋友可以參考下2020-02-02
JavaScript數(shù)據(jù)結(jié)構(gòu)之雙向鏈表
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)之雙向鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
javascript 判斷數(shù)組是否已包含了某個(gè)元素的函數(shù)
javascript判斷數(shù)組是否已包含了某個(gè)元素的js函數(shù),方便數(shù)組的判斷。2010-05-05
利用10行js代碼實(shí)現(xiàn)上下滾動(dòng)公告效果
這篇文章主要給大家介紹了關(guān)于利用10行js代碼實(shí)現(xiàn)滾動(dòng)公告效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起看看吧。2017-12-12
javascript封裝 Cookie 應(yīng)用接口
本文通過(guò)幾個(gè)簡(jiǎn)單的示例向大家展示了javascript封裝cookie的注意事項(xiàng)及操作方法,非常的簡(jiǎn)單實(shí)用,最后附上一則具體實(shí)例,有需要的小火把可以參考下。2015-08-08

