JavaScript 是一門腳本語言,其可以不經(jīng)過編譯而直接運(yùn)行(這點(diǎn)與 Java 不同,Java 需要編譯之后才能運(yùn)行),但是 JavaScript 存在一個(gè)預(yù)編譯的問題。
預(yù)編譯
- JavaScript 腳本的運(yùn)行由兩個(gè)階段組成:預(yù)編譯階段 和 執(zhí)行階段,先進(jìn)行預(yù)編譯,再執(zhí)行語句;
- 預(yù)編譯,簡(jiǎn)單理解,就是在內(nèi)存中開辟一塊空間,用來存放變量和函數(shù)。在 JavaScript 中,JavaScript 會(huì)在內(nèi)存中為使用 <code>var</code> 關(guān)鍵字聲明的變量和使用 <code>function</code> 關(guān)鍵字聲明的函數(shù)開辟一塊空間,用來存放使用這兩者聲明的變量和函數(shù);
- 在預(yù)編譯時(shí),<code>function</code> 的優(yōu)先級(jí)比 <code>var</code> 高,如:
function a(){};
var a = 1;
此時(shí) <code>a</code> 的類型是 <code>function</code>,而不是 <code>number</code>;
- 注意:預(yù)編譯時(shí)并不會(huì)對(duì)變量進(jìn)行賦值(即不會(huì)進(jìn)行初始化),變量賦值是在 JavaScript 腳本執(zhí)行階段進(jìn)行的。
下面我們通過一些例子來理解預(yù)編譯。
實(shí)例1
代碼:
<script>
console.log('before: ' + a); // ①
var a = 1; // ②
console.log('after: ' + a); // ③
</script>
效果演示:

結(jié)果分析:
- JavaScript 腳本在執(zhí)行之前先進(jìn)行預(yù)編譯,所以 ① 和 ③ 不會(huì)執(zhí)行,而是先執(zhí)行 ②,進(jìn)行預(yù)編譯;
- 因?yàn)轭A(yù)編譯階段是不對(duì)變量進(jìn)行賦值的,即不進(jìn)行初始化,所以 ② 也只執(zhí)行前半部分 <code>var a;</code>,由于只聲明了 <code>a</code> 而沒有進(jìn)行賦值,所以此時(shí) <code>a</code> 的值為 <code>undefined</code>;
- 預(yù)編譯完畢之后,JavaScript 腳本開始執(zhí)行,執(zhí)行順序按照從上到下的順序執(zhí)行。
所以,上面的代碼可以拆分成下面的樣子:
<script>
console.log('before: ' + a); // ①
var a; // ②
a = 1; // ③
console.log('after: ' + a); // ④
</script>
首先進(jìn)行預(yù)編譯,先執(zhí)行 ②,之后預(yù)編譯結(jié)束,按照從上到下的順序依次執(zhí)行 ① -> ③ -> ④,執(zhí)行 ① 的時(shí)候,由于此時(shí) <code>a</code> 并未進(jìn)行賦值,其默認(rèn)的值為 <code>undefined</code>,所以輸出 <code>before: undefined</code>,之后執(zhí)行 ③,對(duì) <code>a</code> 進(jìn)行賦值,此時(shí) <code>a = 1</code>,最后執(zhí)行 ④,輸出 <code>after: 1</code>。
實(shí)例2
代碼:
<script>
a(); // ①
console.log(a); // ②
function a(){ // ③
console.log('function');// ④
} // ⑤
console.log(a); // ⑥
a(); // ⑦
</script>
效果演示:

結(jié)果分析:
上面的代碼中我們聲明了一個(gè)函數(shù) <code>a()</code>,按照前面說的,在 JavaScript 腳本執(zhí)行之前會(huì)先進(jìn)行預(yù)編譯,所以函數(shù) <code>a()</code> 會(huì)被預(yù)編譯,之后再執(zhí)行 JavaScript 語句,① 為執(zhí)行函數(shù) <code>a()</code>,② 為在控制臺(tái)輸出函數(shù) <code>a()</code>。
實(shí)例3
代碼:
<script>
a(); // ①
function a(){ // ②
console.log('a'); // ③
} // ④
var a = 1; // ⑤
a(); // ⑥
</script>
效果演示:

結(jié)果分析:
- 預(yù)編譯階段:②③④ 和 ⑤ 的前半部分 <code>var a;</code> 執(zhí)行,由于 <code>function</code> 優(yōu)先級(jí)比 <code>var</code> 高,所以預(yù)編譯結(jié)束之后 <code>a</code> 的類型為 <code>function</code>;
- 執(zhí)行階段:按順序從上往下執(zhí)行,首先執(zhí)行 ①,此時(shí) <code>a</code> 的類型為 <code>function</code>,執(zhí)行沒問題;然后把 <code>1</code> 賦值給 <code>a</code>,由于 JavaScript 是弱類型語言,此時(shí) <code>a</code> 的類型為 <code>number</code>,之后再執(zhí)行函數(shù) <code>a()</code>,會(huì)出現(xiàn)錯(cuò)誤:Uncaught TypeError: a is not a function,因?yàn)?<code>a</code> 此時(shí)本質(zhì)上是一個(gè)變量,而不是函數(shù)。
實(shí)例4
代碼:
<script>
b(); // ①
var b = function () { // ②
console.log('b'); // ③
}; // ④
b(); // ⑤
</script>
效果演示:

結(jié)果分析:
- 預(yù)編譯階段:執(zhí)行 ② 的前半部分 <code>var b;</code>,此時(shí) <code>b</code> 為變量,其值為 <code>undefined</code>(注意:此階段并不會(huì)將匿名函數(shù)賦值給 <code>b</code>,賦值在執(zhí)行階段才進(jìn)行);
- 執(zhí)行階段:首先執(zhí)行 ①,由于 <code>b</code> 的類型為變量,而不是 <code>function</code>,所以會(huì)報(bào)錯(cuò):預(yù)編譯.html:9 Uncaught TypeError: b is not a function,此時(shí)程序出錯(cuò),不再往下執(zhí)行;
- 將 ① 屏蔽,再次運(yùn)行腳本,預(yù)編譯階段還是一樣,執(zhí)行階段:將一個(gè)匿名函數(shù)賦值給變量 <code>b</code>,此時(shí) <code>b</code> 的類型為 <code>function</code>,再執(zhí)行 <code>b()</code>,控制臺(tái)正常輸出 <code>b</code>。
實(shí)例5
代碼:
<script>
function c() { // ①
console.log('c1'); // ②
} // ③
c(); // ④
function c() { // ⑤
console.log('c2'); // ⑥
} // ⑦
</script>
效果演示:

結(jié)果分析:
- 預(yù)編譯階段:先執(zhí)行 ①②③ 和 ⑤⑥⑦,由于 ⑤⑥⑦ 聲明的函數(shù)與 ①②③ 聲明的函數(shù)同名,所以后聲明的函數(shù)會(huì)覆蓋掉之前聲明的函數(shù);
- 執(zhí)行階段:執(zhí)行 ④,由于前面所說,所以在控制臺(tái)會(huì)輸出 <code>c2</code>。
總結(jié)
總之,要理解預(yù)編譯,只要弄清兩點(diǎn):變量/函數(shù)聲明 與 變量賦值。在預(yù)編譯階段,只進(jìn)行 變量/函數(shù)聲明,不會(huì)進(jìn)行變量的初始化(即變量賦值,所有變量的值都是 <code>undefined</code>);變量賦值 是在執(zhí)行階段才進(jìn)行的。