JavaScript之淺談記憶體空間
- 2020 年 4 月 28 日
- 筆記
JavaScript之淺談記憶體空間
JavaScipt 記憶體自動回收機制
在JavaScript中,最獨特的一個特點就是擁有自動的垃圾回收機制(周期性執行),這也就意味者,前端開發人員能夠專註於業餘,從而減少在記憶體的管理,提高開發的效率。
用戶自定義的對象、函數,但這些都是我們肉眼不可見的,而是依靠在外部的媒介「記憶體條」中,自動垃圾回收的本質也就是找出已不再使用的變數、函數,釋放其佔用的記憶體空間
當不再需要某樣東西時會發生什麼? JavaScript 引擎是如何發現並清理它?
可達性
JavaScript 中記憶體管理的主要概念是可達性。
簡單地說,「可達性」 值就是那些以某種方式可訪問或可用的值,它們被保證存儲在記憶體中。
固有的可達值(根)
-
本地函數的局部變數和參數
-
當前嵌套執行上下文其他函數的變數和函數
-
全局變數
如果引用或者引用鏈可以通過根訪問到任何其他值,則認為該值是可訪問的
實際上網頁的元素就是由一個個對象構建成了一個dom樹(特殊的圖結構)(樹結構是單向,圖結構是雙向的,)

通過JavaScipt提供的api我們可以找到頁面上指定元素的對象,並對其進行操作
每一個DOM元素對象都可以看作是一個根,我們可以還可以訪問自身元素的親戚
-
父元素
-
兄弟元素
-
祖先元素
-
後代元素
單項引用
var user = {
name : 'EYS',
}
這裡的箭頭表示一個對象引用,全局變數 “user” 引用對象{name : ‘EYS’} user對象中的name屬性存儲了一個基本類型的數據
但如果user的值被覆蓋,則引用丟失
user = null;
現在user變成不可達的狀態,沒有辦法訪問之前的值,他們之間沒有聯繫,就被JavaScript引擎發現他了!!!然後就把他丟到小黑屋去了,自動釋放了它所佔用的記憶體空間
#### 雙向引用
// user具有對象的引用
var user = {
name: "John"
};
var admin = user; //引用傳遞
該對象仍然可以通過 admin 全局變數訪問,所以它在記憶體中。如果我們也覆蓋admin,那麼它可以被釋放。
相互關聯的對象
function marry (man, woman) {
woman.husban = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
})
產生的記憶體結構:
記憶體中的圖片變成:
現在讓我們刪除兩個引用:
delete family.father;
delete family.mother.husband;
僅僅刪除這兩個引用中的一個是不夠的,因為所有對象仍然是可訪問的。
輸出引用無關緊要。只有傳入的對象才能使對象可訪問,因此,John 現在是不可訪問的,並將從記憶體中刪除所有不可訪問的數據。
垃圾回收之後:
但是如果我們把這兩個都刪除,那麼我們可以看到 John 不再有傳入的引用:
無法訪問的數據塊
family = null;
但是如果我們把這兩個都刪除,那麼我們可以看到 John 不再有傳入的引用:
「family」對象已經從根上斷開了鏈接,不再有對它的引用,因此下面的整個塊變得不可到達,並將被刪除。
回收過程
標記清除 (推薦)
分為『進入環境』和『離開環境』
進入環境 : 指變數進入的執行環境
離開環境 : 指變數完成任務,離開了執行的環境
垃圾收集器會在腳本運行的時候給存儲在記憶體中的所有變數都加上標記
它會去掉環境中的變數以及被環境中的變數引用的變數的標記
而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法訪問到這些變數了
最後,垃圾收集器完成記憶體清除工作,銷毀那些帶標記的值並回收它們所佔用的記憶體空間
引用計數 (不推薦)
含義 : 跟蹤記錄每個值被引用的次數
-
當用戶聲明了一個變數並將一個引用類型值賦給該變數時,則這個值的應用次數就為1 【聲明變數並賦值】
-
如果同一個值又被賦給另一個變數,則該值的引用次數加 1 【變數的值傳遞】
-
如果包含這個值引用的變數被覆蓋了,則之前的值的應用次數減1 【覆蓋變數之前的值】
-
當這個值的引用次數變成 0 時,則說明沒有辦法再訪問這 個值了,因而就可以將其佔用的記憶體空間回收回來。 【變數回收】
這種機制其實在js中並不常用,因為這種機制會產生循環引用的問題,『循環引用』指的是對象 A 中包含一個指向對象 B 的指針,而對象 B 中也包含一個指向對象 A 的引用。對於像js類的自動回收機制的語言來說,需要額外手動的去釋放記憶體,其實並不友好。
在學習記憶體空間之前,我們需要對三種數據結構有一個清晰的理解。他們分別是堆(heap),棧(stack)與隊列(queue)。
三種數據結構
一、棧(Stack)數據結構
JavaScript中並沒有嚴格意義上區分棧記憶體與堆記憶體
如JavaScript的執行上下文(關於執行上下文我會在下一篇文章中總結)。執行上下文的執行順序借用了棧數據結構的存取方式(也就是後面我們會經常提到的函數調用棧)。因此理解棧數據結構的原理與特點十分重要。
JavaScript的數據類型分為兩種 : 基本類型,引用類型
我們可以簡單粗暴的理解 基本類型數據是存儲在棧,引用類型的數據是存儲在堆中,等待變數建立引用關係
要簡單理解棧的存取方式,我們可以通過類比乒乓球盒子來分析。如下圖左側。

基本特徵為 : 先進後出,後進先出
二、堆(Heap)數據結構
堆數據結構是一種樹狀結構。它的存取數據的方式,則與書架與書非常相似。
書雖然也整齊的存放在書架上,但是我們只要知道書的名字,就可以很方便的取出我們想要的書,而不用像從乒乓球盒子里取乒乓一樣,非得將上面的所有乒乓球拿出來才能取到中間的某一個乒乓球。好比在JSON格式的數據中,我們存儲的key-value是可以無序的,因為順序的不同並不影響我們的使用,我們只需要關心書的名字。
三、隊列
隊列是一種先進先出(FIFO)的數據結構。正如排隊過安檢一樣,排在隊伍前面的人一定是最先過檢的人。用以下的圖示可以清楚的理解隊列的原理。
