JavaScript 內存回收機制

引用

垃圾回收算法主要依賴引用的概念,例如一個對象若是有另一個對象的訪問權限,這裏就叫作一個對象引用另一個對象,不論這裏是顯式仍是隱式程序員

回收機制

Js具備自動垃圾回收機制。垃圾收集器會按照固定的時間間隔週期性的執行。第二種程序員本身釋放算法

這個算最簡單的回收算法,大體是某地對象當沒有引用指向它的時候,也就是零指向,這個對象就會被垃圾回收機制回收瀏覽器

let arr = [1,2,3]

arr = null  // [1,2,3]沒有被引用,會被自動回收

標記清除

工做原理:當變量進入環境時,將這個變量標記爲進入環境。當變量離開環境時,則將其標記爲離開環境。標記離開環境的就回收內存。緩存

工做流程數據結構

垃圾回收器在運行的時候會給存儲在內存中的全部變量都加上標記。閉包

去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。app

依然被標記的會被視爲準備刪除的變量。dom

垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收他們所佔用的內存空間。ide

引用計數

工做原理:跟蹤記錄每一個值被引用的次數函數

工做流程

聲明瞭一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1

同一個值又被賦值給另外一個變量,這個引用類型值的引用次數加1.

當包含這個引用類型值的變量又被賦值成另外一個值了,那麼這個引用類型值的引用次數減1.

當引用次數變成0時,說明沒辦法訪問這個值了。

當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所佔的內存。

 

 

 

 

 

 

 

 

 

問題:循環引用
function f() {
    var o1 = {}
    var o2 = {}
    o1.p = o2; // o1 引用o2
    o2.p = o1; // o2引用o1,這裏造成了一個循環引用
}
f()

 

 

 

 

objA指向內存中的引用類型,而這個引用類型的一個值又指向了另外一個引用類型,這樣,每一個引用類型的引用次數都是2,且在引用類型之間造成了循環引用,這樣,即便problem()函數執行完畢,把後期再也不使用的局部變量objA和objB釋放,可是由於引用類型的引用次數仍是1,那麼這兩個引用類型仍是不能被釋放的,這就形成了內存泄露。

 

  • 循環引用其實是在堆中的兩個引用類型之間的循環引用,如右邊的兩個箭頭就構成了一個循環。
  • 由於IE中的BOM、DOM的實現使用了COM,IE的JavaScript引擎使用的是標記清除的策略,可是JavaScript訪問的COM對象依然是基於引用計數的策略的。因此會存在循環引用的問題

 

var element = document.getElementById("some_element");
var myObj =new Object();
myObj.element = element;
element.someObject = myObj;

 

這個例子中,一個DOM元素和一個原生JavaScript對象之間創建了循環引用。這樣,即便例子中的DOM從頁面中移除,內存也不會被回收

解決方法以手動切斷他們的循環引用

 

 

myObj.element = null;
element.someObject =null;

 

內存泄漏

使用了內存以後, 若是後面他們不會再被用到,可是尚未及時釋放,這就叫作內存泄露(memory leak)。若是出現了內存泄露,那麼有可能使得內存愈來愈大,而致使瀏覽器崩潰。

引發內存泄漏的狀況

1. 意外的全局變量引發的內存泄漏。

 

 

function foo(arg) {
    bar = "this is a hidden global variable"; // 沒有用var
  }


 function foo() {
    this.variable = "potential accidental global";
  }
  // 通常函數調用,this指向全局

 

緣由:全局變量,不會被回收。 js中的全局變量,只有當頁面被關閉後纔會銷燬

解決:使用嚴格模式避免,函數內使用var定義,塊內使用letconst 

2. 閉包引發的內存泄漏

緣由:閉包能夠維持函數內局部變量,使其得不到釋放。

 

 

function do(){
    let thing = 'eat'
    return function(){
        console.log(thing)
    }}

 

解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用

三、  刪除元素形成的內存泄露。

<div id="wrap">
    <span id="link">點擊</a>
  </div>
  <script>
    let wrap = document.querySelector('#wrap'),
        link = document.querySelector('#link');
    function handleClick() {
      alert('clicked');
    }
    link.addEventListener('click', handleClick );

    wrap.removeChild(link);
document.body.appendChild(link);
</sript>

 

即便link已經被移除了,而後咱們經過appendChild添加到div平級的地方,而後點擊以後仍是有事件發生的,說明這裏元素被移除而後添加,事件仍是能夠用的。

可是,咱們已經將之移除了,因此,後面就不須要了,可是span標籤仍是被link變量所引用,這樣,就形成了內存泄露。

因此,咱們能夠在link被移除的時候,就清除這個引用,以下所示:

 

 

 

<div id="wrap">
    <span id="link">點擊</a>
  </div>
  <script>
     let wrap = document.querySelector('#wrap'),
        link = document.querySelector('#link')
    function handleClick() {
      alert('clicked');
    }
    link.addEventListener('click', handleClick );

    wrap.removeChild(link);
  link = null

 

緣由:雖然DOM刪除了,可是對象中還存在對dom的引用

解決:手動刪除。

被遺忘的定時器或者回調

 

 

var serverData = loadData();setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }}, 5000);

 

 

緣由:定時器中有dom的引用,即便dom刪除了,可是定時器還在,因此內存中仍是有這個dom** 解決**:手動刪除定時器和dom

子元素存在引用引發的內存泄

緣由div中的ul li , 獲得這個div,會間接引用某個獲得的li,那麼此時由於div間接引用li,即便li被清空,也仍是在內存中,而且只要li不被刪除,他的父元素都不會被刪除。 ** 解決**:手動刪除清空

 

堆內存和棧內存

基本類型是:Undefined/Null/Boolean/Number/String,

基本類型的值存在內存中,被保存在棧內存中。從一個變量向另外一個變量複製基本類型的值,會建立這個值的一個副本。

引用類型:object

引用類型的值是對象,保存在堆內存中。

  1. 包含引用類型值的變量實際上包含的並非對象自己,而是一個指向該對象的指針。從一個變量向另外一個變量複製引用類型的值,複製的實際上是指針,所以兩個變量最終都指向同一個對象。

  2. js不容許直接訪問內存中的位置,也就是不能直接訪問操做對象的內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。

堆和棧的區別

1、堆棧空間分配區別:

一、棧(操做系統):由操做系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧;

二、堆(操做系統): 通常由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式卻是相似於鏈表。

2、堆棧緩存方式區別:

一、棧使用的是一級緩存, 他們一般都是被調用時處於存儲空間中,調用完畢當即釋放;

二、堆是存放在二級緩存中,生命週期由虛擬機的垃圾回收算法來決定(並非一旦成爲孤兒對象就能被回收)。因此調用這些對象的速度要相對來得低一些。

3、堆棧數據結構區別:

 

堆(數據結構):堆能夠被當作是一棵樹,如:堆排序;   棧(數據結構):一種先進後出的數據結構。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

回收機制

Js具備自動垃圾回收機制。垃圾收集器會按照固定的時間間隔週期性的執行。第二種程序員本身釋放

這個算最簡單的回收算法,大體是某地對象當沒有引用指向它的時候,也就是零指向,這個對象就會被垃圾回收機制回收

let arr = [1,2,3]

arr = null  // [1,2,3]沒有被引用,會被自動回收

 

標記清除

工做原理:當變量進入環境時,將這個變量標記爲進入環境。當變量離開環境時,則將其標記爲離開環境。標記離開環境的就回收內存。

工做流程

垃圾回收器在運行的時候會給存儲在內存中的全部變量都加上標記。

去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。

依然被標記的會被視爲準備刪除的變量。

垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收他們所佔用的內存空間。

引用計數

工做原理:跟蹤記錄每一個值被引用的次數

工做流程

聲明瞭一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1

同一個值又被賦值給另外一個變量,這個引用類型值的引用次數加1.

當包含這個引用類型值的變量又被賦值成另外一個值了,那麼這個引用類型值的引用次數減1.

當引用次數變成0時,說明沒辦法訪問這個值了。

當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所佔的內存。

 

 

 

問題:循環引用

function f() {

var o1 = {}

var o2 = {}

o1.p = o2; // o1 引用o2

o2.p = o1; // o2引用o1,這裏造成了一個循環引用

}

f()

型,而這個引用類型的一個值又指向了另外一個引用類型,這樣,每一個引用類型的引用次數都是2,且在引用類型之間造成了循環引用,這樣,即便problem()函數執行完畢,把後期再也不使用的局部變量objAobjB釋放,可是由於引用類型的引用次數仍是1,那麼這兩個引用類型仍是不能被釋放的,這就形成了內存泄露。

·  循環引用其實是在堆中的兩個引用類型之間的循環引用,如右邊的兩個箭頭就構成了一個循環。

·  由於IE中的BOMDOM的實現使用了COMIEJavaScript引擎使用的是標記清除的策略,可是JavaScript訪問的COM對象依然是基於引用計數的策略的。因此會存在循環引用的問題

內存泄漏

本質上來講,內存泄漏就是不被須要的內存,由於某種緣由,沒有被回收或者釋放

使用了內存以後, 若是後面他們不會再被用到,可是尚未及時釋放,這就叫作內存泄露(memory leak)。若是出現了內存泄露,那麼有可能使得內存愈來愈大,而致使瀏覽器崩潰

引發內存泄漏的狀況

1. 意外的全局變量引發的內存泄漏。

 function foo(arg) {

    bar = "this is a hidden global variable"; // 沒有用var  }

 

 

 function foo() {

    this.variable = "potential accidental global";

  }

  // 通常函數調用,this指向全局

緣由:全局變量,不會被回收。 js中的全局變量,只有當頁面被關閉後纔會銷燬

解決:使用嚴格模式避免,函數內使用var定義,塊內使用letconst 

2. 閉包引發的內存泄漏

緣由:閉包能夠維持函數內局部變量,使其得不到釋放。

function do(){

    let thing = 'eat'

    return function(){

        console.log(thing)

    }}

 

解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用

三、  刪除元素形成的內存泄露。

<div id="wrap">

    <span id="link">點擊</a>

  </div>

  <script>

    let wrap = document.querySelector('#wrap'),

        link = document.querySelector('#link');

    function handleClick() {

      alert('clicked');

    }

    link.addEventListener('click', handleClick );

 

    wrap.removeChild(link);

document.body.appendChild(link);

即便link已經被移除了,而後咱們經過appendChild添加到div平級的地方,而後點擊以後仍是有事件發生的,說明這裏元素被移除而後添加,事件仍是能夠用的。

可是,咱們已經將之移除了,因此,後面就不須要了,可是span標籤仍是被link變量所引用,這樣,就形成了內存泄露。

因此,咱們能夠在link被移除的時候,就清除這個引用,以下所示:

 <div id="wrap">

    <span id="link">點擊</a>

  </div>

  <script>

     let wrap = document.querySelector('#wrap'),

        link = document.querySelector('#link')

    function handleClick() {

      alert('clicked');

    }

    link.addEventListener('click', handleClick );

 

    wrap.removeChild(link);

link = null

緣由:雖然DOM刪除了,可是對象中還存在對dom的引用

解決:手動刪除。

被遺忘的定時器或者回調

var serverData = loadData();setInterval(function() {

    var renderer = document.getElementById('renderer');

    if(renderer) {

        renderer.innerHTML = JSON.stringify(serverData);

    }}, 5000);

 

緣由:定時器中有dom的引用,即便dom刪除了,可是定時器還在,因此內存中仍是有這個dom** 解決**:手動刪除定時器和dom

子元素存在引用引發的內存泄

緣由div中的ul li , 獲得這個div,會間接引用某個獲得的li,那麼此時由於div間接引用li,即便li被清空,也仍是在內存中,而且只要li不被刪除,他的父元素都不會被刪除。 ** 解決**:手動刪除清空

相關文章
相關標籤/搜索