眾所周知,JavaScript 是一門(mén)弱類(lèi)型的編程語(yǔ)言,也因此它的內(nèi)存管理機(jī)制顯得尤為重要。在沒(méi)有手動(dòng)回收的情況下,內(nèi)存泄漏會(huì)讓我們的程序變得異常緩慢,并帶來(lái)各種不可預(yù)計(jì)的后果。所以,了解 JavaScript 的內(nèi)存管理機(jī)制是非常必要的。
JavaScript 的內(nèi)存分為堆內(nèi)存和棧內(nèi)存。在函數(shù)執(zhí)行時(shí),會(huì)開(kāi)辟一個(gè)新的??臻g,并在頂部記錄當(dāng)前函數(shù)的執(zhí)行狀態(tài)。當(dāng)函數(shù)體內(nèi)聲明了變量時(shí),這個(gè)變量會(huì)被存放到棧內(nèi)存中。而對(duì)于對(duì)象、數(shù)組等復(fù)雜數(shù)據(jù)類(lèi)型,它們則會(huì)被放在堆內(nèi)存中。
function foo() { var a = 'Hello World'; var b = [1, 2, 3]; var c = { x: 0, y: 1 }; }
在上述代碼中,變量 a 會(huì)被存放在棧內(nèi)存中,而 b 和 c 則會(huì)被存放在堆內(nèi)存中。在函數(shù)執(zhí)行完畢后,變量 a 會(huì)隨著函數(shù)的彈出而被自動(dòng)銷(xiāo)毀。而對(duì)于 b 和 c,它們的生命周期并不止于該函數(shù)體,所以需要手動(dòng)管理它們的生命周期。
當(dāng)我們使用 var 或 let 聲明變量時(shí),變量會(huì)被賦予默認(rèn)值 undefined,并將該變量存放在棧內(nèi)存中。而當(dāng)我們給變量賦值時(shí),這個(gè)變量實(shí)際上是存儲(chǔ)了一個(gè)指向堆內(nèi)存中數(shù)據(jù)的指針。當(dāng)變量不再使用時(shí),我們常常使用 delete 操作符來(lái)釋放對(duì)堆內(nèi)存的引用,從而讓它成為垃圾,等待垃圾回收器回收。
var a = { x: 0, y: 1 }; a = null; //此刻 a 指向 null,不再對(duì)之前的堆內(nèi)存進(jìn)行引用,等待垃圾回收。
還有一種常見(jiàn)的內(nèi)存泄漏情況是閉包的使用。在 JavaScript 中,閉包是指一個(gè)能夠訪(fǎng)問(wèn)另一個(gè)函數(shù)作用域內(nèi)變量的函數(shù)。這種情況下,當(dāng)被閉包引用的變量所在的作用域成為垃圾時(shí),該變量仍然被閉包持有,不會(huì)被垃圾回收器回收。
function foo() { var a = 0; function bar() { console.log(a); } return bar; } var baz = foo(); //返回一個(gè)閉包函數(shù) bar baz(); //0 baz = null; //此刻 baz 指向 null,但 a 仍然被閉包引用。
以上是 JavaScript 內(nèi)存管理機(jī)制的常見(jiàn)情況。當(dāng)然,JavaScript 有自己的垃圾回收機(jī)制,在 V8 引擎中,垃圾回收器是采用 Scavenge 和 Mark-Sweep 兩種方式進(jìn)行的。Scavenge 算法適用于年輕代內(nèi)存,它將內(nèi)存分為 From 空間和 To 空間,當(dāng) From 空間存滿(mǎn)時(shí)會(huì)觸發(fā)垃圾回收,將未被引用的對(duì)象回收。Mark-Sweep 算法適用于既包含年輕代又包含老年代的 Heap。當(dāng)年輕代空間無(wú)法容納對(duì)象時(shí),會(huì)將對(duì)象晉升到老年代。
JavaScript 內(nèi)存管理機(jī)制雖然相對(duì)其他編程語(yǔ)言更加復(fù)雜,但精通了它同樣能夠幫助我們寫(xiě)出更高效、更可靠的代碼。