JavaScript引擎
JavaScript引擎就是一段可以"讀懂"JavaScript代碼,並且給出代碼運行結果的程序。
對於靜態語言(如:C、C++、Java),處理上述這些事情的叫編譯器Compiler
。對於JavaScript這樣的動態語言則被稱為解釋器Interpreter
。不同的地方在於編譯器是將源代碼編譯為另外一種代碼(比如:機器碼、或者字節碼),而解釋器是直接解析並將代碼運行結果輸出。
但是JavaScript引擎很難定界算是解釋器還是編譯器。Chrome JS
引擎V8
是用C++
編寫的,為了提高瀏覽器執行JavaScript的性能,V8
將JavaScript轉為了更高效的機器碼(JIT編譯器:Just-In-Time compiler),而不是使用解釋器。
JavaScript執行
JavaScript代碼的執行過程大致可以分為語法檢查和運行兩個階段
語法檢查
語法檢查包括詞法分析和語法分析
- 詞法分析,會將字符組成的字符串分解為有意義的代碼塊。這些代碼塊被稱為詞法單元。
例如:對於程序var a = 2;
,會被分解為 var
a
=
2
;
- 語法分析,會將詞法單元流轉換成一個由元素逐級嵌套所組成的代表了程序語法結構的樹。
這個樹被稱為`抽象語法樹(Abstract Syntax Tree,AST)
例如:上面的的詞法單元流var
a
=
2
;
會被轉為下方所示的AST
運行階段
運行階段包括預編譯和執行
-
預編譯,將生成
AST
復制到當前執行的上下文中。對當前AST
中的變量聲明、函數聲明及函數形參進行屬性填充。 -
JavaScript逐行讀取並運行代碼
聲明提前
聲明提前(hoisting),指的是JavaScript會默認將聲明移到作用域頂部。意味着我們可以在聲明前就使用。
- 對於
var
聲明的變量只是將聲明提前到頂部,賦值還是在位置
console.log(name); //undefined;不會報錯
var name = "chenjy";
console.log(name); //chenjy
- 對於
function
聲明的函數,會將函數名稱和函數體都提前
foo(); //chenjy
function foo(){
console.log("chenjy");
}
- 變量聲明早於函數
foo(); //chenjy
function foo(){
console.log("chenjy");
}
var foo = function(){
console.log("Tom");
};
foo(); //2
function foo(){
console.log("1");
}
foo(); //2
function foo(){
console.log("2");
}
foo(); //2
var foo = function(){
console.log("3");
};
foo(); //3
聲明提前在預編譯階段完成。
let 、const不適用 聲明提前
JavaScript異步
JavaScript
是單線程語言
如果有兩個進程操作一個
dom
,同時下達了2個矛盾的指令編輯
和刪除
,瀏覽器會不知道怎樣去執行指令。
但是單線程就意味着,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等着。對於用戶來說就會出現卡死
的
情況,用戶的體驗就會很差。
但是單線程怎樣實現異步呢?
任務隊列
JavaScript設計者
發現很多時候排隊並不是因為計算量過大cpu
忙不過來,而是因為IO設備很慢(比如:Ajax操作從網絡讀取數據)不得不等到結果,再往下執行。
所以此時主線程可以完全不管IO設備,而是把等待中的任務掛起,先執行后面的任務。等到IO設備返回結構后,再繼續執行掛起的任務。
JavaScipt中所有任務可以被分為兩種一種是同步任務synchronous
,另一種是異步任務asynchronous
。同步任務指的是在主線程上排隊執行的任務。異步任務指的就是那些不進入主線程,而進入任務隊列的任務,只有任務隊列
通知主線程,某個異步任務可以執行了,它才會進入主線程。
主線程的工作機制可以理解為:
-
所有同步任務在主線程執行,形成一個執行棧(execution context stack)
-
在主線程外還存在一個任務隊列(task queue),只要異步任務有了返回結果就會在任務隊列中放置一個事件
-
所有的執行棧中任務執行完畢以后,開始讀取任務隊列中的事件,並且結束對應的異步任務,進入執行棧,開始執行
-
主線程不斷的重復第三步
主線程從任務隊列讀取事件的過程是循環不斷的,所以這種機制又被稱為Event Loop
(事件循環)
定時器
除了放置異步任務的事件,任務隊列還可以放置定時事件。
定時器主要由setTimeout()和setInterval()這兩個函數來完成,二者的內部運行機制完全一樣。
例如setTimeout(fn,time)
指定某個任務最早在主線程空閑多少時間執行。要等到同步任務和任務隊列現有的事件都處理完,才會得到執行。
console.log("1",new Date());
setTimeout(function(){
console.log("3",new Date());
},3000);
console.log("2",new Date());
即使設置的
time
為0
setTimeout(function(){
console.log("2",new Date());
},0);
console.log("1",new Date());