咱們一般將 JavaScript 歸類爲動態或解釋執行語言,但實際上它也是一門編譯語言,它有本身的編譯器形式,運行在 JavaScript 引擎
中。web
每一個 Web 瀏覽器都有本身的 JavaScript 引擎形式:Chrome 有 V8
,Mozilla 有 SpiderMonkey
等。這些 JavaScript 引擎的共同點都是將 JavaScript 代碼轉換爲編譯器能夠理解的語言,而後執行它。瀏覽器
當 JavaScript 代碼運行的時候,運行 JavaScript 代碼的環境造成了執行上下文 ,執行上下文決定代碼能夠訪問哪些變量、函數、對象等。微信
咱們將執行上下文簡單視爲運行當前代碼的 environment / scope
,咱們知道做用域分爲 global scope
和 local scope
。app
相似的,執行上下文也分爲不一樣的類型:ide
全局執行上下文 - 代碼首次執行時候的默認環境,在代碼的整個執行過程當中,只用一個全局執行上下文。函數
函數執行上下文 - 每當執行流程進入到一個函數體內部的時候,就會建立一個函數執行上下文,能夠有任意數量的函數執行上下文。this
JavaScript 是單線程的,瀏覽器只分配給 JavaScript 一個主線程,一次只能執行一個任務(函數),所以它在執行棧中對其餘操做(事件和函數執行)造成一個任務隊列,排隊等候執行。spa
每當在瀏覽器中加載腳本時,棧 stack
中的第一個元素就是全局執行上下文
。當有函數執行時,將建立一個函數執行上下文
,並將其置於全局執行上下文
之上。一旦函數執行完成,它就會從執行堆棧中彈出,並將控制權交給它下面的上下文中。結合上面說到的,咱們看一個例子:線程
var name = "global variable"; console.log(name) function func1() { console.log("func1 被調用了。") func2(); } function func2() { console.log("func2 被調用了。"); } func1();
當上述代碼在瀏覽器中加載時:指針
global execution context
並將其推送到當前執行棧。func1()
被調用,而後 Javascript 引擎爲該函數建立一個新的函數執行上下文 function execution context
並將其推送到全局執行上下文的頂部。func1()
過程當中,發現 func2()
被調用,Javascript 引擎爲該函數建立一個新的執行上下文,並將其推送到 func1()
執行上下文的頂部。func2()
函數完成時,其執行上下文從當前堆棧彈出,將控制權交給其下面的執行上下文,即 func1()
函數執行上下文。func1()
完成後,其執行堆棧將從堆棧中刪除,將控制權交給全局執行上下文。一旦執行了全部代碼,JavaScript 引擎就會從當前堆棧中刪除全局執行上下文。執行上下文主要有兩個階段:建立階段
和執行階段
,接下來咱們將逐一進行介紹。
在函數執行發生以前,JavaScript 引擎會作以下事情:
scope chain
。做用鏈告訴執行上下文它應該包含什麼,以及它應該在哪裏查找解析函數的引用和變量的值。(對於全局環境,外部環境爲 null。在全局做用域內的全部環境都將把全局環境做爲其外部環境)。環境存儲器
,其中全局上下文
的建立和引用(Web瀏覽器中的窗口),變量、函數和函數參數的建立和引用在內存
中完成。this
關鍵字的值(對於全局執行上下文,this 指向 window)。咱們能夠將建立階段
使用僞代碼這樣表示:
creationPhase = { // 建立階段 'outerEnvironmentConnection': { // 建立外部鏈接 // 造成做用域鏈 }, 'variableObjectMapping': { // 變量、函數和函數參數的建立和引用在內存中完成。 }, 'valueOfThis': {}, // 肯定 this 的值 }
這是代碼在建立階段
造成的執行上下文中的運行的階段,而且逐行分配變量值。
當執行開始時,JavaScript 引擎在其建立階段對象中查找執行函數的引用。若是在當前對象中沒有找到,它將沿着做用域鏈
繼續向上查找,直到它到達全局環境。
若是在全局環境中找不到函數引用,則將返回錯誤。若是找到了引用而且函數正確執行,那麼這個特定函數的執行上下文
將從棧中彈出,接着 JavaScript 引擎將移動到下一個函數,它們的函數執行上下文
將被加入到棧中並執行,以此類推。
讓咱們經過示例來看看上面的兩個階段,以便更好地理解它。
let name = "webinfoq"; var title = "execution context"; const message = "hello world"; function func1(num) { var author = "deepak"; let value = 3; let func2 = function multiply() { return num * value; } const fixed = "Divine"; function addFive() { return num + 5; } } func1(10);
所以,全局執行上下文
將以下表示:
globalExecutionObj = { // 全局執行s上下文 outerEnvironmentConnection: null, // 全局上下文外部環境爲 null variableObjectMapping: { name: uninitialized, // 在建立階段,let聲明的變量是未初始化狀態 title: undefined, // var 聲明的變量表示爲未定義 date: uninitialized, // 在建立階段,const聲明的變量是未初始化狀態 func1: <func1 reference>, func1 地址引用 }, this: window //Global Object }
注意:let
和const
定義的變量在建立階段沒有任何與之關聯的值,但var
定義的變量在建立階段爲undefined
,
這就是爲何能夠在va
r 聲明以前訪問變量,(獲得的是undefined
), 在let
和const
聲明以前訪問會報錯(暫時性死區)。
這就是所謂的變量提高
,全部使用 var
聲明的變量都會被提高到做用域的頂部。
在執行階段
,完成對變量的賦值等操做。所以,在執行階段
,全局執行上下文global execution
看起來像這樣:
globalExectutionObj = { // 全局執行上下文 outerEnvironmentConnection: null, variableObjectMapping: { name: "webinfoq", title: "execution context", message: "hello world", func1: pointer to function func1, // 指向func1的指針 }, this: window //Global Object }
當執行到 func1()
時,將造成新的函數執行上下文 function execution global
,建立階段以下所示:
func1ExecutionObj = { // func1 函數執行上下文 outerEnvironmentConnection: Global, // 外部環境爲全局環境 variableObjectMapping: { arguments: { 0: 10, length: 1 }, num: 10, author: undefined, // var 聲明的 value: uninitialized, // let 聲明的 func2: uninitialized, // let 聲明的 fixed: uninitialized, // const 聲明 addFive: pointer to function addFive() // 指向函數addFive的指針 }, this: Global Object or undefined }
執行階段:
func1ExecutionObj = { outerEnvironmentConnection: Global, variableObjectMapping: { arguments: { // 先處理 arguments 參數 0: 10, length: 1 }, num: 10, author: "deepak", //變量f賦值 val: 3, func2: pointer to function func2() fixed: "Divine" addFive: pointer to function addFive() }, this: Global Object or undefined }
Javascript 引擎建立執行上下文
,調用棧
。當有函數執行時,引擎就會建立一個新的函數執行上下文
。最後所用函數執行完成後,將更新全局環境,而後全局代碼完成,程序結束。
瞭解更多請關注微信公衆號:webinfoq
。