作用域、閉包、內存泄露


  作用域

  作用域指的是變量的有效訪問范圍。作用域對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()函數創建后,它的內部屬性指向一個作用域鏈,該作用域鏈中被填入一個單獨的可變對象,這個可變對象是一個全局對象。此全局對象包含諸如窗口、瀏覽器和文檔之類的訪問接口。

  

  而當add函數運行時會建立一個內部對象,稱作“運行期上下文”。一個運行期上下文定義了一個函數運行時的環境。對函數的每次運行而言,每個運行期上下文都是獨一的,所以多次調用同一個函數就會導致多次創建運行期上下文(execution context)。當函數執行完畢,運行期上下文就被銷毀。

  一個運行期上下文有它自己的作用域鏈,用於標識符解析。當運行期上下文被創建時,它的作用域鏈被初始化。包括函數的[[Scope]]屬性中所包含的對象會按照它們出現在作用域鏈中的順序,被復制到運行期上下文的作用域鏈中。這項工作一旦完成,一個被稱作“激活對象”的新對象就為運行期上下文創建好了。此激活對象作為函數執行期的一個可變對象,包含訪問所有局部變量,命名參數,參數集合,和this的接口。然后,此對象被推入作用域鏈的前端。當作用域鏈被銷毀時,激活對象也一同銷毀。下圖顯示了add函數運行時所對應的運行期上下文和它的作用域鏈。

var total = add(5, 10);

  

  需要記住的是兩點:

  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的這些版本中發生循環引用時便會導致內存泄露。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM