堆與棧
我們都知道:在計(jì)算機(jī)領(lǐng)域中,堆棧是兩種數(shù)據(jù)結(jié)構(gòu),它們只能在一端(稱為棧頂(top))對(duì)數(shù)據(jù)項(xiàng)進(jìn)行插入和刪除。
堆:隊(duì)列優(yōu)先,先進(jìn)先出;由操作系統(tǒng)自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
棧:先進(jìn)后出;動(dòng)態(tài)分配的空間 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收,分配方式倒是類似于鏈表。
棧內(nèi)存(變量對(duì)象)與基本數(shù)據(jù)類型
JavaScript中的數(shù)據(jù)類型大致分為,基本數(shù)據(jù)類型與引用數(shù)據(jù)類型
- 基本類型:Undefined、Null、Boolean、Number 和 String,這5中基本數(shù)據(jù)類型可以直接訪問,他們是按照值進(jìn)行分配的,存放在棧(stack)內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,數(shù)據(jù)大小確定,內(nèi)存空間大小可以分配。
- 引用類型:即存放在堆(heap)內(nèi)存中的對(duì)象,變量實(shí)際保存的是一個(gè)指針,這個(gè)指針指向另一個(gè)位置。
demo01
var a = 20;
var b = a;
b = 30;
console.log(a);//20
上面這段代碼指的是,在變量對(duì)象中執(zhí)行數(shù)據(jù)復(fù)制的時(shí)候,其實(shí)系統(tǒng)會(huì)自動(dòng)為新的變量在棧中分配一個(gè)新的值,所以a與b其實(shí)已經(jīng)是完全獨(dú)立的兩個(gè)變量,只是值一樣而已。

堆內(nèi)存與引用數(shù)據(jù)類型
javascript中的引用數(shù)據(jù)類型是存放在堆內(nèi)存中的,但是不同于變量對(duì)象,javascript是不允許直接訪問堆內(nèi)存中的數(shù)據(jù),所以如果我們要訪問引用數(shù)據(jù)類型的時(shí)候,采用的是按引用訪問,其實(shí)就是在變量對(duì)象中存放了一個(gè)指向?qū)ο蟮木浔梢岳斫鉃橐粋€(gè)地址,要訪問堆內(nèi)存中的對(duì)象,就要通過這個(gè)引用句柄來訪問,例如上圖中的d變量,就是一個(gè)指向?qū)ο蟮牡刂贰?/p>
demo02
var m = { a:10,b:20};
var n = m; // 也就是我們熟知的淺拷貝
n.a = 15;
console.log(m.a);//15
上面這段代碼指的是執(zhí)行引用類型數(shù)據(jù)的復(fù)制時(shí),在變量對(duì)象中會(huì)分配一個(gè)新的值,來存放新的變量,但是這兩個(gè)變量的地址是一樣的,相當(dāng)于指向的對(duì)象是一樣的,所以各自改變對(duì)象里面的屬性值,會(huì)互相影響,如下圖

內(nèi)存泄漏
程序的運(yùn)行需要內(nèi)存。只要程序提出要求,操作系統(tǒng)或者運(yùn)行時(shí)(runtime)就必須供給內(nèi)存。
對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程(daemon),必須及時(shí)釋放不再用到的內(nèi)存。否則,內(nèi)存占用越來越高,輕則影響系統(tǒng)性能,重則導(dǎo)致進(jìn)程崩潰。不再用到的內(nèi)存,沒有及時(shí)釋放,就叫做內(nèi)存泄漏(memory leak)。
內(nèi)存生命周期
不管什么程序語言,內(nèi)存生命周期基本是一致的:
- 分配你所需要的內(nèi)存
- 使用分配到的內(nèi)存(讀、寫)
- 不需要時(shí)將其釋放\歸還
在所有語言中第一和第二部分都很清晰。最后一步在底層語言中很清晰,但是在像JavaScript 等上層語言中,這一步是隱藏的。
垃圾回收機(jī)制
垃圾回收機(jī)制怎么知道,哪些內(nèi)存不再需要呢?
最常使用的方法叫做"引用計(jì)數(shù)"(reference counting):語言引擎有一張"引用表",保存了內(nèi)存里面所有的資源(通常是各種值)的引用次數(shù)。如果一個(gè)值的引用次數(shù)是0,就表示這個(gè)值不再用到了,因此可以將這塊內(nèi)存釋放。

上圖中,左下角的兩個(gè)值,沒有任何引用,所以可以釋放。
如果一個(gè)值不再需要了,引用數(shù)卻不為0,垃圾回收機(jī)制無法釋放這塊內(nèi)存,從而導(dǎo)致內(nèi)存泄漏。
const arr = [1, 2, 3, 4];
console.log('hello world');
上面代碼中,數(shù)組[1, 2, 3, 4]是一個(gè)值,會(huì)占用內(nèi)存。變量arr是僅有的對(duì)這個(gè)值的引用,因此引用次數(shù)為1。盡管后面的代碼沒有用到arr,它還是會(huì)持續(xù)占用內(nèi)存。
如果增加一行代碼,解除arr對(duì)[1, 2, 3, 4]引用,這塊內(nèi)存就可以被垃圾回收機(jī)制釋放了。
let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;
上面代碼中,arr重置為null,就解除了對(duì)[1, 2, 3, 4]的引用,引用次數(shù)變成了0,內(nèi)存就可以釋放出來了。
在局部作用域中,等函數(shù)執(zhí)行完畢,變量就沒有存在的必要了,js垃圾回收機(jī)制很快做出判斷并且回收,但是對(duì)于全局變量,很難判斷什么時(shí)候不用,所以,經(jīng)驗(yàn)之談就是,盡量少使用全局變量。
我們?cè)谑褂瞄]包的時(shí)候,就會(huì)造成嚴(yán)重的內(nèi)存泄漏,因?yàn)殚]包的原因,局部變量會(huì)一直保存在內(nèi)存中,所以在使用閉包的時(shí)候,要多加小心。