任何 JavaScript 代碼片斷在執行前都要進行編譯(一般就在執行前)。所以,JavaScript編譯器首先會對 var a = 2; 這段程序進行編譯,而後作好執行它的準備,而且一般立刻就會執行它。node
編譯通常分爲三步:編程
碼塊被稱爲詞法單元(token)。例如,考慮程序 var a = 2;。這段程序一般會被分解成
爲下面這些詞法單元:var、a、=、2 、;。空格是否會被看成詞法單元,取決於空格在
這門語言中是否具備意義。數組
這個過程是將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的表明了程序語法
結構的樹。這個樹被稱爲「抽象語法樹」 (Abstract Syntax Tree,AST)。
var a = 2; 的抽象語法樹中可能會有一個叫做VariableDeclaration 的頂級節點,接下
來是一個叫做Identifier(它的值是a)的子節點,以及一個叫做AssignmentExpression
的子節點。AssignmentExpression 節點有一個叫做 NumericLiteral(它的值是 2)的子
節點。promise
將 AST 轉換爲可執行代碼的過程被稱爲代碼生成。這個過程與語言、目標平臺等息息
相關。
拋開具體細節,簡單來講就是有某種方法能夠將var a = 2; 的AST轉化爲一組機器指
令,用來建立一個叫做 a 的變量(包括分配內存等),並將一個值儲存在a 中。瀏覽器
js引擎是單線程的,編譯和執行js的線程只有一個。
nodejs和瀏覽器還有別的線程用於處理其它任務,如處理AJAX請求的線程、處理DOM事件的線程、定時器線程、讀寫文件的線程。異步
單線程因此有了事件循環。async
函數調用造成一個棧幀,放入當前執行棧中,幀中包含了當前context須要的參數和局部變量。編程語言
一個 JavaScript 運行時包含了一個待處理的消息隊列。每個消息都關聯着一個用以處理這個消息的函數。函數
其它線程將消息放到消息隊列,js線程經過事件循環過程去獲取消息。spa
在事件循環期間的某個時刻,運行時從最早進入隊列的消息開始處理隊列中的消息。爲此,這個消息會被移出隊列,並做爲輸入參數調用與之關聯的函數。
函數的處理會一直進行到執行棧再次爲空爲止;而後事件循環將會處理隊列中的下一個消息。
執行邏輯很簡單,就是先清空當前context的micortask,再執行task
一個例子:
console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) Promise.resolve().then(r=>console.log(16)) async function a1(){ console.log('13') await console.log('14') console.log('15') } process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) a1() setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) })