1. 執行環境 execution context
當JavaScript引擎執行代碼時,它會創建執行環境。而每個執行環境都有兩個階段:創建階段與執行階段。
(1). 執行環境的創建階段
在創建階段中,JavaScript引擎會執行以下幾個任務:
- 首先,創建全局對象(如:瀏覽器的全局對象為window)。
- 其次,創建this對象來綁定全局對象。
- 再次,設置一個内存堆來存儲變量和函數引用。
- 最後,在内存堆中存儲函數聲明,在全局執行環境中存儲變量,變量的初始值為未定義。
在這些任務都完成后,則進入執行階段。
(2). 執行環境的執行階段
在執行階段,JavaScript引擎逐行執行代碼,為變量賦值,並執行函數調用。
對於每一個函數的調用,JavaScript引擎都會創建一個新的函數執行環境。函數執行環境與全局執行環境相類似,區別在於創建的不是全局對象,而是參數對象,它是對函數所有參數的引用。
2. 調用棧 call stack
- JavaScript引擎使用調用棧來管理執行環境。
- 調用棧使用基於後進先出原則工作的堆棧數據結構。也就是説,后加入的工作會被優先處理。
如:function minus (a, b) {
return a - b;
}
function isNegative(a, b) {
return minus (a, b) < 0;
}
console.log(isNegative(2, 3)); // true
以上代碼中,調用棧的步驟大概是這樣的:
1. main()
2. console.log()
3. isNegative()
4. minus()
因爲遵循後進先出原則,所以JavaScript的執行順序大概是這樣的:
1. minus()
2. isNegative()
3. console.log()
4. main()
3. 事件循環 event loop
JavaScript是一個單綫程程序語言,但是它可以在事件循環的基礎上實現并發模型。
事件循環是一個不斷運行的過程,它協調調用棧和回調隊列之間的任務,以實現并發性。
步驟大概是這樣的:
Call Stack (JavaScript Runtime) ---> Web API ---> Callback Queue ---> Event Loop ---> Call Stack (JavaScript Runtime)
在回調隊列(Callback Queue)裏排隊的程序只有當調用棧(Call Stack)中空了以後,才會通過事件循環(Event Loop)傳到調用棧(Call Stack)中去執行。
4. 吊裝 hoisting
當JavaScript引擎在創建全局執行環境階段,會將變量的聲明與函數的聲明放到代碼的最頂端,JavaScript的這種操作特點被稱之爲吊裝。
(1). 變量吊裝
如:let a = 1, b = 2;
console.log(result); // undefined
let result = a + b;
這種賦值後於調用的騷操作之所以沒有報錯,是因爲JavaScript引擎在處理以上代碼時的順序是這樣的:
// 首先,JavaScript已經把所有變量的聲明都放到了代碼的最頂端。
let a, b, result;
// 其次,再執行這段代碼的第一行。由於,result沒有被賦值,所以 JavaScript引擎賦給它一個初始值undefined。
a = 1, b = 2, result = undefined;
// 所以,當打印result值時沒有報錯,而是直接給出了result的初始值。
console.log(result); // undefined
(2). 函數吊裝
如:console.log(greeting()); // Hi, there.
function greeting() {
return 'Hi, there.';
}
與變量吊裝一樣,這個騷操作之所以沒有報錯,是因爲JavaScript引擎先把greeting函數的聲明放到了所有代碼的最頂端。
即,在JavaScript看來,執行順序是這樣的:
// 第一步是函數的聲明
function greeting() {
return 'Hi, there.';
}
// 第二步才開始逐行執行代碼
console.log(greeting());
- 注意事項:當遇到函數表達式時,由於函數已被賦值給某個變量,所以在調用該變量時遵循的就不是函數吊裝,而是變量吊裝。
如:console.log(a()); // a is not a function
let a = function greeting() {
return 'Hi, there';
}