執行上下文和執行棧屬於js引擎的執行過程的預編譯階段。前端
執行上下文是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念。能夠理解爲當執行代碼時作的準備工做。git
執行上下文按照運行環境被分紅三類:github
全局執行上下文(JS代碼加載完畢後,進入代碼預編譯即進入全局環境)瀏覽器
函數環境執行上下文(函數調用執行時,進入該函數環境,不一樣的函數則函數環境不一樣)安全
eval執行上下文(不建議使用,會有安全,性能等問題)ide
對於每一個執行上下文,都有三個重要屬性:函數
VO:變量對象(Variable object);post
AO:活動對象(activation object),當進入函數執行上下文時,這個函數執行上下文的變量對象纔會被激活,因此才叫 activation object,而只有被激活的變量對象,也就是活動對象上的各類屬性才能被訪問(進入函數上下文時建立活動對象)。性能
執行棧,也叫調用棧,具備 LIFO(後進先出)結構,用於存儲在代碼執行期間建立的全部執行上下文。this
首次運行JS代碼時,會建立一個全局執行上下文並Push到當前的執行棧中。每當發生函數調用,引擎都會爲該函數建立一個新的函數執行上下文並Push到當前執行棧的棧頂。
根據執行棧LIFO規則,當棧頂函數運行完成後,其對應的函數執行上下文將會從執行棧中Pop出,上下文控制權將移到當前執行棧的下一個執行上下文。
全局執行上下文在瀏覽器關閉或者標籤關閉時纔會出棧。
1 var a = 'Hello World!'; 2 3 function first() { 4 console.log('Inside first function'); 5 second(); 6 console.log('Again inside first function'); 7 } 8 9 function second() { 10 console.log('Inside second function'); 11 } 12 13 first(); 14 console.log('Inside Global Execution Context'); 15 16 // Inside first function 17 // Inside second function 18 // Again inside first function 19 // Inside Global Execution Context
建立階段就是肯定三個重要上下文屬性的過程。
建立arguments對象,檢查當前上下文中的參數,創建該對象的屬性與屬性值,僅在函數環境(非箭頭函數)中進行,全局環境沒有此過程
檢查當前上下文的函數聲明,按代碼順序查找,將找到的函數提早聲明,若是當前上下文的變量對象沒有該函數名屬性,則在該變量對象以函數名創建一個屬性,屬性值則爲指向該函數所在堆內存地址的引用,若是存在,則會被新的引用覆蓋。
檢查當前上下文的變量聲明,按代碼順序查找,將找到的變量提早聲明,若是當前上下文的變量對象沒有該變量名屬性,則在該變量對象以變量名創建一個屬性,屬性值爲undefined;若是存在,則忽略該變量聲明
變量提高的緣由:
在建立階段,函數聲明存儲在環境中,而變量會被設置爲 undefined
(在 var
的狀況下)或保持未初始化(在 let
和 const
的狀況下)。因此這就是爲何能夠在聲明以前訪問 var
定義的變量(儘管是 undefined
),但若是在聲明以前訪問 let
和 const
定義的變量就會提示引用錯誤的緣由。這就是所謂的變量提高。
從建立順序看,函數提高優先於變量提高。
由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈。
當函數建立的時候,函數有一個內部屬性會保存全部父變量對象到其中。
進入函數上下文,建立 VO/AO 後,就會將活動對象添加到做用鏈的前端。
因此:
做用域鏈的第一項永遠是當前做用域(當前上下文的變量對象或活動對象);
最後一項永遠是全局做用域(全局執行上下文的活動對象);
做用域鏈保證了變量和函數的有序訪問,查找方式是沿着做用域鏈從左至右查找變量或函數,找到則會中止查找,找不到則一直查找到全局做用域,再找不到則會拋出引用錯誤。
全局執行上下文中變量對象的this屬性指向爲window,函數上下文則較爲複雜,之後會詳細介紹。
完成對全部變量的分配,最後執行代碼。
參考文檔: