javascript 垃圾回收機制

---------------------這是學習筆記---------------------javascript

隨着前端業務需求的不斷增多,相比之前,咱們會佔用更多的內存。可是內存並非無限的,而對於那些咱們再也不須要的變量、對象該怎麼處理呢?難道一個一個去手動釋放麼?其實並不須要,Javascript 具備自動垃圾回收機制,會按期對那些咱們再也不使用的變量、對象所佔用的內存進行釋放前端

Javascript 的垃圾回收機制

Javascript 會找出再也不使用的變量,再也不使用意味着這個變量生命週期的結束。Javascript 中存在兩種變量——全局變量和局部變量,所有變量的聲明週期會一直持續,直到頁面卸載java

而局部變量聲明在函數中,它的聲明週期從執行函數開始,直到函數執行結束。在這個過程當中,局部變量會在堆或棧上被分配相應的空間以存儲它們的值,函數執行結束,這些局部變量也再也不被使用,它們所佔用的空間也就被釋放數組

可是有一種狀況的局部變量不會隨着函數的結束而被回收,那就是局部變量被函數外部的變量所使用,其中一種狀況就是閉包,由於在函數執行結束後,函數外部的變量依然指向函數內的局部變量,此時的局部變量依然在被使用,因此也就不可以被回收瀏覽器

function func1 () {
      const obj = {}
}

function func2 () {
      const obj = {}
      return obj
}

const a = func1()
const b = func2()
複製代碼

上面這個例子中,func1 執行時爲 obj 分配了一塊內存,可是隨着函數執行結束,obj佔用的空間也就被釋放了;而 func2 執行時,也爲 obj 分配了內存,可是因爲 obj 最終被返回賦值給了 b 致使其依然被使用,因此 func2 中的 obj 佔用的內存不會被釋放bash

垃圾回收的兩種實現方式

垃圾回收有兩種實現方式,分別是標記清除引用計數閉包

標記清楚

當變量進入執行環境時標記爲「進入環境」,當變量離開執行環境時則標記爲「離開環境」,被標記爲「進入環境」的變量是不能被回收的,由於它們正在被使用,而標記爲「離開環境」的變量則能夠被回收函數

function func3 () {
      const a = 1
      const b = 2
      // 函數執行時,a b 分別被標記 進入環境
}

func3() // 函數執行結束,a b 被標記 離開環境,被回收
複製代碼

引用計數

統計引用類型變量聲明後被引用的次數,當次數爲 0 時,該變量將被回收學習

function func4 () {
      const c = {} // 引用類型變量 c的引用計數爲 0
      let d = c // c 被 d 引用 c的引用計數爲 1
      let e = c // c 被 e 引用 c的引用計數爲 2
      d = {} // d 再也不引用c c的引用計數減爲 1
      e = null // e 再也不引用 c c的引用計數減爲 0 將被回收
}
複製代碼

可是引用計數的方式,有一個相對明顯的缺點——循環引用ui

function func5 () {
      let f = {}
      let g = {}
      f.prop = g
      g.prop = f
      // 因爲 f 和 g 互相引用,計數永遠不可能爲 0
}
複製代碼

像上面這種狀況就須要手動將變量的內存釋放

f.prop = null
g.prop = null
複製代碼

在現代瀏覽器中,Javascript 使用的方式是標記清楚,因此咱們無需擔憂循環引用的問題

什麼是內存泄露?

本質上講, 內存泄露就是再也不被須要的內存, 因爲某種緣由, 沒法被釋放.

常見的內存泄露案例

1)全局變量照成內存泄露

function fn() {
   		name = "你我貸"
             }
   	     console.log(name)複製代碼


在 JS 中處理未被聲明的變量, 上述範例中的會把 name , 定義到全局對象中, 在瀏覽器中就是 window 上. 在頁面中的全局變量, 只有當頁面被關閉後纔會被銷燬. 因此這種寫法就會形成內存泄露, 固然在這個例子中泄露的只是一個簡單的字符串, 可是在實際的代碼中, 每每狀況會更加糟糕.

另一種意外建立全局變量的狀況.

function fn() {
   			this.name = "你我貸"
   		}
   		console.log(name)複製代碼


在這種狀況下this被指向了全局變量 window, 意外的建立了全局變量. 咱們談到了一些意外狀況下定義的全局變量, 代碼中也有一些咱們明肯定義的全局變量. 若是使用這些全局變量用來暫存大量的數據, 記得在使用後, 對其從新賦值爲 null.

2)未銷燬的定時器和回調函數照成內存泄露

function fn() {
    		return 2
    	}
    	var oTxt = fn();
   	setInterval(function() {
	    var oHtml = document.getElementById("test")
	    if(oHtml) {
	        oHtml.innerHTML = oTxt;
	    }
	}, 1000); // 每 1 秒調用一次複製代碼

若是後續 oHtml 元素被移除, 整個定時器實際上沒有任何做用. 但若是你沒有回收定時器, 整個定時器依然有效, 不但定時器沒法被內存回收, 定時器函數中的依賴也沒法回收. 在這個案例中的 fn 也沒法被回收.

3 ) 閉包照成內存泄露

在 JS 開發中, 咱們會常常用到閉包, 一個內部函數, 有權訪問包含其的外部函數中的變量. 下面這種狀況下, 閉包也會形成內存泄露.


3)DOM 引用照成內存泄露

不少時候, 咱們對 Dom 的操做, 會把 Dom 的引用保存在一個數組或者 Map 中.

var elements = {
    		txt: document.getElementById("test")
    	}
    	function fn() {
    		elements.txt.innerHTML = "1111"
    	}
    	function removeTxt() {
    		document.body.removeChild(document.getElementById('test'));
    	}
    	fn();
    	removeTxt()
    	console.log(elements.txt)複製代碼


上述案例中, 即便咱們對於 test 元素進行了移除, 可是仍然有對 test 元素的引用, 依然沒法對齊進行內存回收. 另外須要注意的一個點是, 對於一個 Dom 樹的葉子節點的引用. 舉個例子: 若是咱們引用了一個表格中的 td 元素, 一旦在 Dom 中刪除了整個表格, 咱們直觀的以爲內存回收應該回收除了被引用的 td 外的其餘元素. 可是事實上, 這個 td 元素是整個表格的一個子元素, 並保留對於其父元素的引用. 這就會致使對於整個表格, 都沒法進行內存回收. 因此咱們要當心處理對於 Dom 元素的引用.

相關文章
相關標籤/搜索