做用域、閉包、內存泄露

  做用域前端

  做用域指的是變量的有效訪問範圍。做用域對Javascript有重要意義,瞭解做用域的工做原理是在性能角度和功能角度理解Javascript的關鍵。瀏覽器

  每個JavaScript函數都被表示爲對象,是一個函數實例。如下兩種定義函數的方式是等價的。閉包

var sayName = function(){
    alert('hello world!');
}

var sayName = new Function('alert("hello world!")');

  函數對象正如其餘對象那樣,擁有能夠被Javascript代碼訪問的屬性,和一系列不能被Javascript代碼訪問,僅供JavaScript引擎使用的內部屬性。其中一個內部屬性是[[Scope]]。函數

  內部[[Scope]]屬性包含一個函數被建立的做用域中對象的集合。此集合被稱爲函數的做用域鏈,它決定哪些數據可由函數訪問。此函數做用域鏈中的每一個對象被稱爲一個可變對象,每一個可變對象都以「鍵值對」的形式存在。當一個函數建立後,它的做用域鏈被填充以對象,這些對象表明建立此函數的環境中可訪問的數據。例以下面這個全局函數:性能

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}

  當add()函數建立後,它的內部屬性指向一個做用域鏈,該做用域鏈中被填入一個單獨的可變對象,這個可變對象是一個全局對象。此全局對象包含諸如窗口、瀏覽器和文檔之類的訪問接口。this

  

  而當add函數運行時會創建一個內部對象,稱做「運行期上下文」。一個運行期上下文定義了一個函數運行時的環境。對函數的每次運行而言,每一個運行期上下文都是獨一的,因此屢次調用同一個函數就會致使屢次建立運行期上下文(execution context)。當函數執行完畢,運行期上下文就被銷燬。spa

  一個運行期上下文有它本身的做用域鏈,用於標識符解析。當運行期上下文被建立時,它的做用域鏈被初始化。包括函數的[[Scope]]屬性中所包含的對象會按照它們出如今做用域鏈中的順序,被複制到運行期上下文的做用域鏈中。這項工做一旦完成,一個被稱做「激活對象」的新對象就爲運行期上下文建立好了。此激活對象做爲函數執行期的一個可變對象,包含訪問全部局部變量,命名參數,參數集合,和this的接口。而後,此對象被推入做用域鏈的前端。看成用域鏈被銷燬時,激活對象也一同銷燬。下圖顯示了add函數運行時所對應的運行期上下文和它的做用域鏈。3d

var total = add(5, 10);

  

  須要記住的是兩點:code

  1. [[scope]]屬性是函數建立到運行時一直存在的,知道函數被銷燬後佔用的內存纔會被釋放
  2. 運行期上下文只存在於函數運行期間,函數運行結束後該對象被銷燬

  

  閉包、內存泄露對象

  閉包是JavaScript最強大的一個方面,它容許函數訪問局部範圍以外的數據。

function assignEvents() {
    var id = "xdi9592";
    document.getElementById('save').onclick = function(event){
        saveDocument(id);
    }
}

  當assignEvents()被執行時,一個激活對象被建立,幷包含了該函數做用域內全部可訪問的變量和函數,其中包括id變量。它將成爲運行期上下文做用域鏈上的第一個對象,全局對象是第二個。當閉包建立時,[[Scope]]屬性包含了做用域內全部對象的集合(等於assignEvents運行期上下文的做用域鏈,即assignEvents的激活對象,全局對象)。

  

  因爲閉包的[[Scope]]屬性包含與運行期上下文做用域鏈相同的對象引用,會產生反作用。一般,一個函數的激活對象與運行期上下文一同銷燬。當涉及閉包時,運行期上下文對象,以及他的做用域鏈被銷燬,但激活對象就沒法銷燬,由於引用仍然存在於閉包的[[Scope]]屬性中。除非手動接觸全部對匿名函數的引用,等到垃圾收集器下次運行時,assignEvents的激活對象纔會隨着匿名函數一同被銷燬。

document.getElementById('save').onclick = null;

  因此在Javascript代碼中閉包與非閉包函數相比,須要更多內存開銷。在大型網頁應用中,這可能會致使內存泄露與難以排查的性能問題。

  隨着瀏覽器的升級,大部分瀏覽器對於閉包引發的循環引用問題都可以順利解決。但IE9以前使用非本地JavaScript對象實現DOM對象,對於Javascript對象跟DOM對象使用不一樣的垃圾收集器。因此閉包在IE的這些版本中發生循環引用時便會致使內存泄露。

相關文章
相關標籤/搜索