Javascript 的執行環境(execution context)和做用域(scope)及垃圾回收

這是我參與8月更文挑戰的第7天,活動詳情查看:8月更文挑戰javascript

執行環境分爲全局執行環境和函數執行環境,每次進入一個新執行環境,都會建立一個搜索變量和函數的做用域鏈。函數的局部環境不只有權訪問函數做用於中的變量,並且能夠訪問其外部環境,直到全局環境。全局執行環境只能訪問全局執行環境的變量和函數,不能直接訪問局部環境中的信息;變量的執行環境有助於肯定什麼時候應該釋放內存。離開做用域的值會被標記爲能夠回收,將在垃圾收集期間被刪除。javascript中有「標記清楚」 和 「引用計數」 兩種垃圾收集算法。前端

  執行環境 (execution context)java

  執行環境是javascript中很重要的一個概念。變量或函數的 execution context 定義它們有權訪問的其它數據,以及變量或函數各自的行爲。每一個執行環境都有一個與之關聯的 變量對象(variable object) ,環境中定義的全部變量和函數都保存在這個對象中。咱們編寫的代碼沒法訪問到這個對象,可是解析器在處理數據時會在後臺使用它。算法

  全局執行環境是最外圍的一個執行環境,根據 ECMAScript 實現坐在的宿主環境的不一樣,表示執行環境的對象也不一樣。瀏覽器中,全局執行環境被認爲是window對象,全部的全局變量和函數都做爲window對象的屬性或方法存在。瀏覽器

  某個執行環境中的代碼一旦執行完畢,該環境會被銷燬,保存在其中的變量、函數都將隨之銷燬。每一個函數都有一個本身的執行環境。全局執行環境會直到應用程序退出纔會被銷燬,好比關閉網頁。markdown

  做用域鏈 (scope chain)函數

  當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈。做用域鏈的做用是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈能夠看做是一個個變量對象的有機、有序鏈接,做用域鏈的第一個對象,始終都是當前執行的代碼所在環境的 變量對象。做用域鏈的下一個對象來自其外部環境,而在下一個對象則來自下一個外部環境,這樣一直延續到全局執行環境。做用域鏈的最後一個對象始終都是全局執行環境的變量對象對象。若是執行環境是一個函數,則將其活動對象(activation object)做爲變量對象。活動對象在最開始只包含一個變量,即arguments對象(全局環境中不存在該對象)。post

  標識符的解析就是沿着做用域鏈一級一級的搜索標識符的過程,知道找到標識符,若是標識符未被找到則會致使錯誤發生。以下所示:spa

var name = "Zhang San";
 
function changeName() {
    var newName = "Li Si";
 
    function execute(){
        var tempName = newName;
        newName = name;
        name = tempName;
 
        // 能夠訪問 name / newName / tempName
    }
    execute();
    // 能夠訪問 name / newName , 不能訪問 tempName
}
changeName();
// 只能訪問 name

複製代碼

示例涉及3個執行環境:全局環境、changeName()的局部環境和 execute() 的局部環境。內部環境能夠經過做用域鏈訪問全部的外部環境,可是外部環境不能訪問內部環境中的任何變量和函數。這些環境之間的聯繫是線性、有序的。每一個環境均可以向上搜索做用域,以查詢變量和函數名;但都不能向下搜索做用域鏈而進入另外一個執行環境。code

  做用域鏈延長

  儘管執行環境總共有全局執行環境和局部執行環境(函數)兩種,但仍是有一些語句會使做用域鏈的前段臨時增長一個變量對象,改變量對象會在代碼執行後被移除。兩種狀況下會發生這種現象,當執行流進入下列任一語句時,做用域鏈就會獲得加長:

  1. catch塊
  2. with語句

  沒有塊級做用域

  javascript 沒有塊級做用域。在其餘類 C 的語言中,由大括號封閉的代碼都有本身的做用域,能夠實現自根據條件來定義變量。

if(true){
    var name = "Zhang San";
}
alert(name);    // "Zhan San"
複製代碼

  如上例所示,變量 name 被添加到了當前的執行環境(即全局環境)中,若是在 C/C++/JAVA 的語言中變量 name 則會在if語句執行完畢後被銷燬。下面在看一個例子:

for(var i = 0; i < 10; i++){
    // code ...
}
alert(i);     //

複製代碼

  上例中,變量 i 並無隨着循環的結束而被銷燬,而是被定義在了當前執行環境(全局環境)中,這一點要尤其注意。

  聲明變量

  使用 var 關鍵字聲明的變量會被自動添加到最接近的執行環境中,若是在函數內部,最接近的執行環境就是函數的局部環境;在with語句中,最接近的環境是函數環境。若是沒有使用 var 關鍵字定義變量,那麼該變量會被添加到全局環境中。

function getName(){
    var Name = "Zhang San";
    globalName = "Li Si";
    return Name;
}
getName(); // "Zhang San"
alert(globalName);    // "Li Si"
複製代碼

標識符查詢

  當在某個環境中爲了讀取或寫入而引用一個標識符時,必須經過搜索來肯定該標識符的值。搜索過程從做用域鏈的最前端開始逐級向上查找,直到匹配到變量名。在這條鏈路上一旦找到標識符則查詢工做當即結束,變量就緒。搜索過程會一直追溯到全局環境,若是在全局環境中仍未匹配到改查詢則說明變量爲聲明。

  垃圾回收

  javascript 具有垃圾回收機制,執行環節會負責管理代碼執行過程當中使用的內存。開發人員幾乎沒必要去關注內存使用問題,分不配和回收內存徹底實現了自動管理。垃圾回收機制的原理是:找出那些再也不繼續使用的變量,而後釋放其佔用的內存。垃圾收集器會按固定的時間間隔週期性的執行這一操做。

  函數中局部變量的生命週期:局部變量只在函數執行過程當中存在,在這個過程當中會爲局部變量分配內存以存儲他們的值,而後函數中使用這些變量進行一系列操做。當函數執行完畢,這些局部變量也就沒有存在的價值了,能夠釋放他們的內存一共未來使用。

  垃圾收集器必須跟蹤變量的狀態,以肯定它們是否已經不會再被使用,對於再也不使用的變量打上標記,以備未來收回其佔用的內存。如此一來,標識無用變量的策略就顯得尤其重要,具體到瀏覽器中,一般有兩種策略。

  1. 標記清除(Mark-and-Sweep)

  最經常使用的垃圾收集策略就是標記清除,當一個變量進入執行環境(context),例如在函數中生命了一個變量,則這個變量會被標記爲進入執行環境( begin in context),理論上標記爲進入環境的變量是不能被回收的。當變量離開執行環境,則將其標記爲離開環境(out of context)。

  當垃圾收集器運行時,會爲全部存儲在內存中的變量加上標記,而後取消 在當前執行環境中的變量和被當前執行環境中變量引用的變量 的標記。如今,全部被加上標記的變量都被認爲是能夠刪除的變量,接着,垃圾收集器會便開始了他的內存清除工做,收回那些無效變量佔用的內存。

  不一樣瀏覽器都實用類相似的垃圾收集策略,只不過垃圾收集器運行的時間間隔不一樣。

  2. 引用計數(reference counting)

  另外一種垃圾收集機制是引用計數,這種策會跟蹤記錄每一個值被引用的次數,當聲明一個變量並賦值一個引用類型值時,這個值的引用次數就加會1。若是,包含對這個值的引用被取消,或者該變量有被賦值爲其它值,則這個引用類型的值的引用次數就會減1。若是這個引用類型的值的引用次數爲0,那麼它將被垃圾回收機制回收。

  這種回收策略有一個限制,就是當代碼中存在循環引用時,相關對象的引用計數就永遠不會爲0,對象也就沒法被回收。除非開發人員,明確的爲相關對象解除引用。

相關文章
相關標籤/搜索