執(zhí)行環(huán)境(Execution Context)
JS代碼可以歸為下面三種情況之一:
- 全局代碼:首先執(zhí)行這里的代碼
- 函數(shù)代碼
- Eval代碼:eval()函數(shù)中的文本

默認(rèn)有一個(gè)全局執(zhí)行環(huán)境,只能有一個(gè)全局執(zhí)行環(huán)境。
執(zhí)行函數(shù)會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,可以有多個(gè)函數(shù)執(zhí)行環(huán)境。
執(zhí)行環(huán)境棧(Execution Context Stack)

JS引擎默認(rèn)進(jìn)入全局執(zhí)行環(huán)境執(zhí)行全局代碼,如果在全局代碼中調(diào)用了一個(gè)函數(shù),就會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并添加到棧的頂部,JS引擎始終執(zhí)行棧頂部的執(zhí)行環(huán)境,一旦函數(shù)執(zhí)行完成,當(dāng)前的執(zhí)行環(huán)境就會(huì)被彈出, JS引擎繼續(xù)執(zhí)行下一個(gè)執(zhí)行環(huán)境,直到再次到達(dá)全局執(zhí)行環(huán)境。
執(zhí)行環(huán)境內(nèi)部詳情
調(diào)用執(zhí)行環(huán)境分兩個(gè)階段
1.創(chuàng)建階段
- 創(chuàng)建作用域鏈
- 創(chuàng)建變量、函數(shù)和參數(shù)
- 確定this的值
2.執(zhí)行階段
從上到下逐行執(zhí)行代碼。
從概念上來看,執(zhí)行環(huán)境就像一個(gè)對(duì)象,包含三個(gè)屬性
executionContextObj = {
// 作用域鏈
'scopeChain': { /* variableObject + all parent execution context's variableObject */ },
// 變量對(duì)象
'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },
'this': {}
}
執(zhí)行環(huán)境的執(zhí)行流程如下:
創(chuàng)建執(zhí)行環(huán)境
進(jìn)入創(chuàng)建階段
初始化作用域鏈
創(chuàng)建變量對(duì)象
創(chuàng)建arguments對(duì)象,arguments對(duì)象創(chuàng)建與參數(shù)名相同的屬性,以引用的方式指向參數(shù)
function a(x, y) {
console.log(arguments); // { '0': 1, '1': 2 }
x = 3;
y = 4;
console.log(arguments); // { '0': 3, '1': 4 }
}
a(1, 2);
掃描環(huán)境內(nèi)的函數(shù)聲明
每發(fā)現(xiàn)一個(gè)函數(shù)聲明,在變量對(duì)象中創(chuàng)建與函數(shù)名相同的屬性,值是指向這個(gè)函數(shù)的指針
如果這個(gè)函數(shù)名已存在,則重寫這個(gè)指針的值
掃描環(huán)境內(nèi)的變量聲明
每發(fā)現(xiàn)一個(gè)變量聲明,在變量對(duì)象中創(chuàng)建與變量名相同的屬性,值初始化為undefined
如果變量名已存在,則什么也不做
確定this的值
進(jìn)入執(zhí)行階段
逐行執(zhí)行代碼
事件循環(huán)(Event Loop)
JS是單線程,為了不阻塞線程,JS通過事件循環(huán)的方案解決耗時(shí)任務(wù)。

任務(wù)分兩種:
- 宏任務(wù)(Macrotask):script、setTimeout、setInterval、setImmediate、I/O、UI交互事件
- 微任務(wù)(Microtask):Promise、process.nextTick、MutaionObserver
對(duì)應(yīng)的任務(wù)隊(duì)列分別是宏任務(wù)隊(duì)列(Macrotask Queue)和微任務(wù)隊(duì)列(Microtask Queue)。
一開始執(zhí)行的<script>就屬于宏任務(wù)。
Event Loop將一個(gè)宏任務(wù)壓入執(zhí)行環(huán)境棧,在執(zhí)行環(huán)境棧執(zhí)行的過程中,如果遇到任務(wù),則放到相應(yīng)的任務(wù)隊(duì)列中,全部出棧后,清空所有的微任務(wù),依此循環(huán)往復(fù)。
注意,微任務(wù)中會(huì)優(yōu)先清空next tick queue,即通過 process.nextTick 注冊(cè)的函數(shù)。
timer中的0ms和1ms
不管是瀏覽器還是Node,最低延時(shí)是1ms
setTimeout(function () {
console.log(0);
}, 1);
setTimeout(function () {
console.log(1);
}, 0);
打?。? 1
原因如下(Blink源碼和Node源碼):
// https://chromium.googlesource.com/chromium/blink/+/master/Source/core/frame/DOMTimer.cpp#93
double intervalMilliseconds = std::max(oneMillisecond, interval * oneMillisecond);
if (!(after >= 1 && after <= TIMEOUT_MAX))
after = 1; // schedule on next tick, follows browser behavior
Node中的Event Loop
在 Node 中,如果延時(shí)相同,則會(huì)被合并成一個(gè)任務(wù)
setTimeout(function () {
new Promise(function (resolve) {
console.log(1);
resolve();
}).then(function () {
console.log(2);
});
});
setTimeout(function () {
new Promise(function (resolve) {
console.log(3);
resolve();
}).then(function () {
console.log(4);
});
});
在 Chrome 打?。? 2 3 4
但在 Node 中打印:1 3 2 4