詳見:http://www.cnblogs.com/foodoi...html
JavaScript在執行一個「代碼段」以前,即解析(預處理)階段,會先進行一些「準備工做」,例如掃描JS中var定義的變量、函數名等,進而生成執行上下文。閉包
JS中的「代碼段」分爲三種:全局代碼段、函數體代碼段、eval代碼段。(注:ES6以前,JS不存在「代碼塊」做用域的概念,即除了函數以外全部「{}」裏的代碼,都屬於全局做用域)函數
全局代碼段「準備工做」包括:ui
1.變量、函數表達式 —— 變量聲明,默認賦值爲undefined; 2.this —— 賦值; 3.函數聲明 —— 賦值。
函數體代碼段「準備工做」包括:this
1.變量、函數表達式 —— 變量聲明,默認賦值爲undefined; 2.this —— 賦值; 3.函數聲明 —— 賦值; 4.參數 —— 賦值; 5.argument —— 賦值; 6.自由變量的取值做用域 —— 賦值。
evel()不推薦使用,因此再也不分析evel代碼段。spa
至此,「執行上下文」的定義能夠通俗化爲 —— 在執行代碼段以前(預處理階段),把將要用到的全部變量都事先拿出來,有的直接賦值,有的先用undefined佔個空,這些變量共同組成的詞法環境,即爲執行上下文環境。code
在執行js代碼時,會有數不清的函數調用次數,會產生許多個上下文環境。這麼多上下文環境該如何管理,以及如何銷燬並釋放內存呢?這就須要「執行上下文棧」來解釋了。htm
經過上文咱們知道:預處理全局代碼時,會產生一個執行上下文環境。每次調用函數的預處理時,都會產生一個執行上下文環境。其實,當這個函數調用完成時,它的執行上下文環境以及其中的數據就會被銷燬,執行過程再從新回到全局上下文環境。同一時刻,處於活動狀態的執行上下文環境只有一個。對象
實現這一壓棧出棧過程的機制就是「執行上下文棧」。blog
執行上下文棧的壓棧出棧過程實例代碼:
var a = 10, //1.進入全局上下文環境 fn, bar = function(x) { var b = 5; fn(x+b); //3.進入fn函數上下文環境 }; fn = function(y) { var c = 5; console.log(y+c); } bar(10); //2.進入bar函數上下文環境
預處理時,首先建立全局上下文環境:
而後執行代碼,全局上下文環境中的變量都被賦值:
當執行到調用bar函數時,跳轉到bar函數內部,對其進行預處理,建立bar函數的執行上下文環境:
並將這個函數上下文環境壓棧,設置爲活動狀態,開始執行bar函數體內代碼:
當執行到調用fn函數時,跳轉到bar函數內部,對其進行預處理,建立fn函數的執行上下文環境,並壓棧,設置爲活動狀態,開始執行fn函數體內代碼:
fn函數執行完畢後,這次調用fn所生成的上下文環境出棧,而且被銷燬(已經用完了,就要及時銷燬,釋放內存),bar函數的執行上下文環境回到活動狀態:
bar函數執行完畢後,調用bar函數所生成的上下文環境出棧,而且被銷燬(已經用完了,就要及時銷燬,釋放內存),全局上下文環境回到活動狀態:
全局代碼執行完成,全局上下文環境出棧,而且銷燬(已經用完了,就要及時銷燬,釋放內存),代碼執行完畢。
以上是一段代碼執行上下文環境的完整變化過程,但有一種狀況的代碼,其執行上下文環境並未按上述過程銷燬,這就是接下來咱們的重點研究對象 —— 閉包!
要談閉包,咱們還得先認識下做用域和自由變量,敬請期待... ...