Javascript的世界中,隱藏了很多內存陷阱,不能得到合理釋放的內存會埋下各種隱患,本文旨在以實用角度去解讀Js涉及到的內存,且看勇士如何斗惡龍~
本文可以看做是之前那篇勇士斗惡龍之沒那么復雜的Js閉包的后續篇,在思考閉包中內存的問題時,有了寫此文的沖動.
學以致用,從實用的角度出發我們最需要關注的就是內存回收用什么用處?我們日常工作中好像不需要我們自己去處理Javascript中的內存,它會自動回 收的.如果你問Javascript內存回收,一定會有TX鄙視的告訴你,這個會自動回收,不需要我們自己處理.
Javascript中的內存,真的不需要我們關注嗎???
通常這種自問自答的結果,就是會給出一個與問題相反的答案,也就是當然需要!Javascript雖然會自動處理內存,但不是完美的,它存在一定的缺陷.另一方面,我們不能因為無知而無畏,因為可能很多莫名其妙的問題就是這樣出現的.
內存-那些我們忽略的就是我們要拯救的
在平時的編碼中我們無時無刻不在執行這樣的一個過程,分配內存 -> 使用內存 -> 回收內存.如此反復的過程在不斷持續着,每一個數字,字符串,對象,數組,方法都占用着計算機的內存.前端編程因為你的Js代碼大部分運行在客戶端瀏覽 器,所以雖然你沒有占用服務器的內存,可是你占用了使用者的內存.即使在硬件配置越來越高的今天,也不能完全保證我們產品的使用者不是古老的機器,況且你 也應該考慮到移動手機的性能.也不是說每一台機器都配有4G以上的內存供你揮霍,我一直認為好的程序員對待內存要保持一個吝嗇的心態.
Javascript在分配內存這一過程中,會根據不同的數據類型進行分配.像基本數據類型會分配在棧內存,引用數據類型分配在堆內存中.引用對於理解內存是一個很重要的概念,我最早對引用的理解來自於C里面的指針,最好找資料仔細了解一下.
|
1
2
3
4
|
var
obj = {name:
"benze"
};
var
car = obj;
//這里car就是指向了obj所創建的那個對象所在的內存空間,也就是說此時obj和car都指向了同一塊內存.
//在內存管理的環境中,如果一個對象有權限去訪問另一個對象,就叫做一個對象引用另一個對象
|
我們平時在使用定義變量,函數或者對象的時候,都在進行各種的內存分配,但是通常不需要寫代碼去回收,因為我們知道它是自動回收的.這里我得強調,自動不代表我們就不要考慮內存回收了.
使用內存-也就是那些讀讀寫寫
關於內存的使用,我的理解就是對於已經開辟好內存空間的那些值,進行一些讀寫操作.這一過程中可能還有對象引用的改變等等,因為這個不是本文重點,不加以贅述.
內存回收 === 屠龍之術?
話說古時候有人散盡家財學的屠龍之術,技成之后卻發現無龍可屠.那Javascript既然有着自動回收的內存管理,我們學習內存回收豈不也是一樣,反正 它能正常回收就行唄,我們管它是怎么回收的呢.但是問題是我之前提到過,Javascript的垃圾回收機制有一定的局限性和缺陷,有一些情況會使得內存 得不到釋放而持續增加,這時候我們就需要人為的處理它.
引用計數的回收機制
上文提到過引用的概念,Javascript的內存回收的算法主要就依賴於引用,當代碼生成一個新的內存駐留項時(如一個對象),系統就會為它開辟一塊內 存空間.因為這個對象可能會被傳遞給其他函數,或者對象.所以可能很多代碼都會指向這個對象的內存空間.javascript的垃圾回收器跟蹤這些指向, 當最后一個指向都被斷開廢棄的時候,這個對象所占用的空間就會被釋放.
這是一種比較簡單並且清晰的算法,看上去感覺沒什么問題,但是如果出現這種情況呢?
|
1
2
3
4
|
var
a = {name:
'a'
};
var
b = {name:
'b'
};
a.bname = b
b.aname = a;
|
代碼看着很別扭是吧,但是如果真有這種情況呢,彼此引用.這樣的情況,javascript的的回收就對a和b沒有辦法了.對於這種循環引用,實在是各類垃圾自動回收的缺陷.
真實情況的內存無法回收
也許上面說的那種情況對於你來說永遠不可能發生,平時注意點也許就避開了,但是總有些情況,也許你寫了很久自己都沒發現.在稍微舊一點版本的IE下,Javascript的對象是通過標記清除的,BOM和DOM對象卻是通過引用計數,涉及到DOM或者BOM的時候就容易出現循環引用.上代碼瞅瞅:
|
1
2
3
4
5
6
|
<span></span>$(document).ready(
function
(){
var
div = document.getElementById(
"mydiv"
);
div.onclick =
function
(){
console.log(
"div"
);
}
});<span></span>
|
當指定的單擊事件處理程序時,創建了一個在其封閉的環境中包含div變量的閉包環境.而div也包含一個指向閉包的引用(onclick屬性自身),這就導致了內存都不能得到釋放.當然解決方法很簡單:
|
1
2
3
4
5
6
7
|
$(document).ready(
function
(){
var
div = document.getElementById(
"mydiv"
);
div.onclick = saydiv;
});
function
saydiv(){
console.log(
"div"
);
}
|
此時因為saydiv函數不在包含div的引用,所以沒有形成循環,內存可以得到釋放.
可能很多人都知道將一個對象置為null,那么它的內存就會回收.這是因為變量的指向了一個null,那么它原來指向的那塊內存空間就會因為沒有被指向,或者說沒有被引用,而被垃圾回收掉.
標記清除-手動內存回收竟然還是屠龍之術?
從2012年起現代瀏覽器中,對於Javascript垃圾回收的機制進行了更新.不再使用引用計數的算法,而是改為使用標記清除的方式.比如定義一個變 量,那么當它進入執行環境時,會被垃圾回收器標記為"進入環境",當其離開環境比如函數執行完畢的時候,標記為"離開環境".垃圾回收機器就會在這些"離 開環境"的變量中挑選出來需要回收掉的變量用於釋放內存.
這存在一個挑選標准,它不會再去計 算引用的數量.而是從全局對象(根節點)開始尋找,找到所有可獲得的對象和所有不可獲得的對象.也就是它從之前判斷"對象是否被需要"變成"對象是否可以 獲得".這么理解,零引用的對象總是不可獲得的,但是不可能獲得的對象不一定零引用.
如此除了在比較低版本的IE的情況下,Javascript的自動回收機制就足以應付大多數情況了.在高級一點的IE中對於內存回收也有很大的進步,所以還是推薦大多數情況下不要手工的回收垃圾.
尾筆
本來不打算寫這段尾筆,不過為了避免本文有虎頭蛇尾的嫌疑,還是要補充說明一下.首先是大部分現代瀏覽器都已經對內存做了很好的處理,所以大多數情況下不 需要我們手工執行.其次本文的目的在於歸納總結,而不是非要寫出什么特殊東東.最后寫此文也是為了給我自己和看過的人提個醒:
- 不要因為是Web的前端就忽視了內存這個因素,更別以為所有人的電腦都會配有2G乃至4G的內存.
- 本文最大的作用是指出一些可能存在的陷阱,不是告訴大家掉進去怎么辦,而是怎樣避免掉進去.
- 循環引用,閉包,DOM操作,這3點是我認為最容易造成內存問題.
- 別因為你從來沒遇到Javascript內存的問題而忽視乃至忘卻它,優秀的程序員應該吝嗇計算機的資源.

