標籤:
執行環境(execution context)是JavaScript最為重要的一個概念,執行環境定義了變數或函數有權訪問的其他資料,決定了它們各自的行為。每一個執行環境都有一個與之關聯的變數對象(variable object),環境中定義的所有變數和函數都儲存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理資料時會在後台使用它。
全域執行環境是最外圍的一個執行環境。在Web瀏覽器中,全域執行環境被認為是window對象,因此所有全域變數和函數都是作為window對象的屬性和方法建立的。
每個函數都有自己的執行環境。那麼每個函數在執行時也就有與之關聯的變數對象,對函數來說,也叫使用中的物件。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。
當代碼在一個環境中執行時,會建立變數對象的一個範圍鏈(scope chain)。範圍鏈的用途就是保證對執行環境有權訪問的所有變數和函數的有序訪問。範圍鏈的前端,始終都是當前執行的代碼所在環境的變數對象。如果當前執行環境是函數,那就是變數對象就是函數的使用中的物件。使用中的物件在一開始只包含一個變數,arguments對象,這個在介紹函數的時候說過。作用鏈域的下一個變數對象來自包含它的外部環境,而下一個變數對象則來自下一個包含環境。這樣,一直延續一、到全域執行環境。全域執行環境的變數對象window始終是作用鏈域中的最後一個對象。
在分析具體例子前,先看一下JavaScript的運行順序。
如果一個文檔流中包含多個script程式碼片段(用script標籤分隔的js代碼或引入的js檔案),它們的運行順序是:
步驟1. 讀入第一個程式碼片段(js執行引擎並非一行一行地執行程式,而是一段一段地分析執行的)
步驟2. 做文法分析,有錯則報語法錯誤(比如括弧不匹配等),並跳轉到步驟5
步驟3. 對var變數和function定義做“預解析”(永遠不會報錯的,因為只解析正確的聲明)
步驟4. 執行程式碼片段,有錯則報錯(比如變數未定義)
步驟5. 如果還有下一個程式碼片段,則讀入下一個程式碼片段,重複步驟2
步驟6. 結束。
著重說明一下上面紅色部分。JavaScript標識符的解析是沿著範圍一級一級地搜尋標識符的過程。搜尋過程始終從範圍的前端開始,也就是從當前執行的代碼所在的環境開始,然後逐級地向後(或者說成向上)回朔,直到找到標識為止,如果找不到通常會發送錯誤。
var arg = 1;function test(){alert(arg); //100 //var arg = 1;}arg = 100;test();上面例子中,當在test函數的當前範圍中沒有找到arg標識符時,就會沿著範圍鏈到上層範圍中,在上層範圍中我們找到了arg變數,在test函數被調用前,arg變數被賦值為100,所以我alert(arg)會打出100。但如果我們把test函數中注釋那行解除注釋,那麼情況就變了。
var arg = 1;function test(){alert(arg); //undefinedvar arg = 1;}arg = 100;test();此時我們的alert(arg)顯示的是undefined,這是因為arg變數被定義在了test函數中當前範圍中,我們執行test函數時在當前範圍中找到了這個變數,那麼我們就不會沿著範圍鏈往上找了。但是在這裡,arg變數被定義在了alert方法後面,所以執行alert方法時,用的是arg的預設值,也就是undefined。上面這段代碼也就等價於:
var arg = 1;function test(){var arg;alert(arg); //undefinedarg = 1;}arg = 100;test();
JavaScript-執行環境及範圍