(注1:如果有問(wèn)題歡迎留言探討,一起學(xué)習(xí)!轉(zhuǎn)載請(qǐng)注明出處,喜歡可以點(diǎn)個(gè)贊哦!)
(注2:更多內(nèi)容請(qǐng)查看我的目錄。)
1. 簡(jiǎn)介
在本系列的第二篇文章JS入門(mén)難點(diǎn)解析2-JS的變量提升和函數(shù)提升中,我們已經(jīng)討論過(guò)。之所以不說(shuō)JS需要編譯,只是它不像其他編譯語(yǔ)言一樣需要翻譯成等價(jià)的另一種語(yǔ)言。但是仍然需要進(jìn)行語(yǔ)法分析和代碼生成,并且通常是立即執(zhí)行。而且,JS的變量提升和函數(shù)提升就發(fā)生在編譯階段。
回顧一下:
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
以及
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
這兩段代碼,前一段進(jìn)行了變量聲明提升,后一段進(jìn)行了函數(shù)聲明提升。我們講到過(guò),這是因?yàn)?JavaScript 編譯器和引擎并非一行一行地分析和執(zhí)行程序,而是一段一段地分析執(zhí)行。當(dāng)分析執(zhí)行一段代碼的時(shí)候,會(huì)進(jìn)行一個(gè)“準(zhǔn)備工作”,包括變量聲明提升和函數(shù)聲明提升等。那么這里所謂的一段指的是什么呢?到底JavaScript編譯器和引擎遇到一段怎樣的代碼時(shí)才會(huì)做“準(zhǔn)備工作”呢?這就需要了解什么是可執(zhí)行代碼了。
2. 可執(zhí)行代碼
JavaScript 的可執(zhí)行代碼(executable code)有以下三類(lèi):全局代碼、函數(shù)代碼、eval代碼。
當(dāng)JS引擎遇到這三類(lèi)代碼時(shí),會(huì)開(kāi)始做準(zhǔn)備工作,創(chuàng)建一個(gè)“執(zhí)行上下文(execution context)"。
舉例說(shuō)明,當(dāng)JS執(zhí)行到一個(gè)函數(shù)的時(shí)候,就會(huì)創(chuàng)建該函數(shù)的“執(zhí)行上下文(execution context)"。那么問(wèn)題來(lái)了,JS代碼中可能出現(xiàn)為數(shù)眾多的函數(shù),如何管理創(chuàng)建的那么多執(zhí)行上下文呢?
3. 執(zhí)行上下文棧
JavaScript 引擎創(chuàng)建了執(zhí)行上下文棧(Execution context stack,ECS)來(lái)管理執(zhí)行上下文。
為了模擬執(zhí)行上下文棧的行為,讓我們定義執(zhí)行上下文棧是一個(gè)數(shù)組:
ECStack = [];
試想當(dāng) JavaScript 開(kāi)始要解釋執(zhí)行代碼的時(shí)候,最先遇到的就是全局代碼,所以初始化的時(shí)候首先就會(huì)向執(zhí)行上下文棧壓入一個(gè)全局執(zhí)行上下文,我們用 globalContext 表示它,并且只有當(dāng)整個(gè)應(yīng)用程序結(jié)束的時(shí)候,ECStack 才會(huì)被清空,所以 ECStack 最底部永遠(yuǎn)有個(gè) globalContext。
ECStack = [
globalContext
];
現(xiàn)在 JavaScript 遇到下面的這段代碼了:
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
執(zhí)行一個(gè)函數(shù)的時(shí)候,就會(huì)創(chuàng)建一個(gè)執(zhí)行上下文,并且壓入執(zhí)行上下文棧,當(dāng)函數(shù)執(zhí)行完畢的時(shí)候,就會(huì)將函數(shù)的執(zhí)行上下文從棧中彈出。知道了這樣的工作原理,讓我們來(lái)看看如何處理上面這段代碼:
// 偽代碼
// fun1()
ECStack.push(<fun1> functionContext);
// fun1中調(diào)用了fun2,還要?jiǎng)?chuàng)建fun2的執(zhí)行上下文
ECStack.push(<fun2> functionContext);
// fun2還調(diào)用了fun3
ECStack.push(<fun3> functionContext);
// fun3執(zhí)行完畢
ECStack.pop();
// fun2執(zhí)行完畢
ECStack.pop();
// fun1執(zhí)行完畢
ECStack.pop();
// javascript接著執(zhí)行下面的代碼,但是ECStack底層永遠(yuǎn)有個(gè)globalContext