­

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)的數據結構。正如排隊過安檢一樣,排在隊伍前面的人一定是最先過檢的人。用以下的圖示可以清楚的理解隊列的原理。