感謝社區(qū)中各位的大力支持橄维,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券训堆,享受所有官網(wǎng)優(yōu)惠做葵,并抽取幸運(yùn)大獎(jiǎng):點(diǎn)擊這里領(lǐng)取
至此护蝶,你應(yīng)當(dāng)對(duì)作用域的想法华烟,以及變量如何根據(jù)它們被聲明的方式和位置附著在不同的作用域?qū)蛹?jí)上感到相當(dāng)適應(yīng)了。函數(shù)作用域和塊兒作用域的行為都是依賴于這個(gè)相同規(guī)則的:在一個(gè)作用域中聲明的任何變量都附著在這個(gè)作用域上持灰。
但是關(guān)于出現(xiàn)在一個(gè)作用域內(nèi)各種位置的聲明如何附著在作用域上盔夜,有一個(gè)微妙的細(xì)節(jié),而這個(gè)細(xì)節(jié)正是我們要在這里檢視的。
先有雞還是先有蛋喂链?
有一種傾向認(rèn)為你在JavaScript程序中看到的所有代碼返十,在程序執(zhí)行的過程中都是從上到下一行一行地被解釋執(zhí)行的。雖然這大致上是對(duì)的椭微,但是這種猜測(cè)中的一個(gè)部分可能會(huì)導(dǎo)致你錯(cuò)誤地考慮你的程序洞坑。
考慮這段代碼:
a = 2;
var a;
console.log( a );
你覺得在console.log(..)
語句中會(huì)打印出什么?
許多開發(fā)者會(huì)期望undefined
蝇率,因?yàn)檎Z句var a
出現(xiàn)在a = 2
之后迟杂,這很自然地看起來像是這個(gè)變量被重定義了,并因此被賦予了默認(rèn)的undefined
本慕。然而排拷,輸出將是2
。
考慮另一個(gè)代碼段:
console.log( a );
var a = 2;
你可能會(huì)被誘導(dǎo)而這樣認(rèn)為锅尘,因?yàn)樯弦粋€(gè)代碼段展示了一種看起來不是從上到下的行為监氢,也許在這個(gè)代碼段中,也會(huì)打印2
藤违。另一些人認(rèn)為浪腐,因?yàn)樽兞?code>a在它被聲明之前就被使用了,所以這一定會(huì)導(dǎo)致一個(gè)ReferenceError
被拋出纺弊。
不幸的是牛欢,兩種猜測(cè)都不正確。輸出是undefined
。
那么疙筹。這里發(fā)生了什么螃成? 看起來我們遇到了一個(gè)先有雞還是先有蛋的問題。哪一個(gè)現(xiàn)有拾稳?聲明(“蛋”),還是賦值(“雞”)腊脱?
編譯器再次襲來
要回答這個(gè)問題访得,我們需要回頭引用第一章關(guān)于編譯器的討論∩掳迹回憶一下悍抑,引擎 實(shí)際上將會(huì)在它解釋執(zhí)行你的JavaScript代碼之前編譯它。編譯過程的一部分就是找到所有的聲明杜耙,并將它們關(guān)聯(lián)在合適的作用域上搜骡。第二章向我們展示了這是詞法作用域的核心。
所以佑女,考慮這件事情的最佳方式是记靡,在你的代碼的任何部分被執(zhí)行之前谈竿,所有的聲明,變量和函數(shù)摸吠,都會(huì)首先被處理空凸。
當(dāng)你看到var a = 2;
時(shí),你可能認(rèn)為這是一個(gè)語句寸痢。但是JavaScript實(shí)際上認(rèn)為這是兩個(gè)語句:var a;
和a = 2;
呀洲。第一個(gè)語句,聲明啼止,是在編譯階段被處理的两嘴。第二個(gè)語句,賦值族壳,為了執(zhí)行階段而留在 原處憔辫。
于是我們的第一個(gè)代碼段應(yīng)當(dāng)被認(rèn)為是這樣被處理的:
var a;
a = 2;
console.log( a );
……這里的第一部分是編譯,而第二部分是執(zhí)行仿荆。
相似地贰您,我們的第二個(gè)代碼段實(shí)際上被處理為:
var a;
console.log( a );
a = 2;
所以,關(guān)于這種處理的一個(gè)有些隱喻的考慮方式是拢操,變量和函數(shù)聲明被從它們?cè)诖a流中出現(xiàn)的位置“移動(dòng)”到代碼的頂端锦亦。這就產(chǎn)生了“提升”這個(gè)名字。
換句話說令境,先有蛋(聲明)杠园,后有雞(賦值)。
注意: 只有聲明本身被提升了舔庶,而任何賦值或者其他的執(zhí)行邏輯都被留在 原處抛蚁。如果提升會(huì)重新安排我們代碼的可執(zhí)行邏輯,那就會(huì)是一場(chǎng)災(zāi)難了惕橙。
foo();
function foo() {
console.log( a ); // undefined
var a = 2;
}
函數(shù)foo
的聲明(在這個(gè)例子中它還 包含 一個(gè)隱含的瞧甩,實(shí)際為函數(shù)的值)被提升了,因此第一行的調(diào)用是可以執(zhí)行的弥鹦。
還需要注意的是肚逸,提升是 以作用域?yàn)閱挝坏?/strong>。所以雖然我們的前一個(gè)代碼段被簡(jiǎn)化為僅含有全局作用域彬坏,但是我們現(xiàn)在檢視的函數(shù)foo(..)
本身展示了朦促,var a
被提升至foo(..)
的頂端(很明顯,不是程序的頂端)栓始。所以這個(gè)程序也許可以更準(zhǔn)確地解釋為:
function foo() {
var a;
console.log( a ); // undefined
a = 2;
}
foo();
函數(shù)聲明會(huì)被提升务冕,就像我們看到的。但是函數(shù)表達(dá)式不會(huì)混滔。
foo(); // 不是 ReferenceError洒疚, 而是 TypeError!
var foo = function bar() {
// ...
};
變量標(biāo)識(shí)符foo
被提升并被附著在這個(gè)程序的外圍作用域(全局),所以foo()
不會(huì)作為一個(gè)ReferenceError
而失敗坯屿。但foo
還沒有值(如果它不是函數(shù)表達(dá)式油湖,而是一個(gè)函數(shù)聲明,那么它就會(huì)有值)领跛。所以乏德,foo()
就是試圖調(diào)用一個(gè)undefined
值,這是一個(gè)TypeError
非法操作吠昭。
同時(shí)回想一下喊括,即使它是一個(gè)命名的函數(shù)表達(dá)式,這個(gè)名稱標(biāo)識(shí)符在外圍作用域中也是不可用的:
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// ...
};
這個(gè)代碼段可以(使用提升)更準(zhǔn)確地解釋為:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
// ...
}
函數(shù)優(yōu)先
函數(shù)聲明和變量聲明都會(huì)被提升矢棚。但一個(gè)微妙的細(xì)節(jié)(可以 在擁有多個(gè)“重復(fù)的”聲明的代碼中出現(xiàn))是郑什,函數(shù)會(huì)首先被提升,然后才是變量蒲肋。
考慮這段代碼:
foo(); // 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
1
被打印了蘑拯,而不是2
!這個(gè)代碼段被 引擎 解釋執(zhí)行為:
function foo() {
console.log( 1 );
}
foo(); // 1
foo = function() {
console.log( 2 );
};
注意那個(gè)var foo
是一個(gè)重復(fù)(因此被無視)的聲明兜粘,即便它出現(xiàn)在function foo()...
聲明之前申窘,因?yàn)楹瘮?shù)聲明是在普通變量之前被提升的。
雖然多個(gè)/重復(fù)的var
聲明實(shí)質(zhì)上是被忽略的孔轴,但是后續(xù)的函數(shù)聲明確實(shí)會(huì)覆蓋前一個(gè)剃法。
foo(); // 3
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}
雖然這一切聽起來不過是一些學(xué)院派的細(xì)節(jié),但是它表明了一個(gè)事實(shí):在同一個(gè)作用域內(nèi)的重復(fù)定義是一個(gè)十分差勁兒的主意路鹰,而且經(jīng)常會(huì)導(dǎo)致令人困惑的結(jié)果贷洲。
在普通的塊兒內(nèi)部出現(xiàn)的函數(shù)聲明一般會(huì)被提升至外圍的作用域,而不是像這段代碼暗示的那樣有條件地被定義:
foo(); // "b"
var a = true;
if (a) {
function foo() { console.log( "a" ); }
}
else {
function foo() { console.log( "b" ); }
}
然而晋柱,重要的是要注意這種行為是不可靠的恩脂,而且是未來版本的JavaScript將要改變的對(duì)象,所以避免在塊兒中聲明函數(shù)可能是最好的做法趣斤。
復(fù)習(xí)
我們可能被誘導(dǎo)而將var a = 2
看作是一個(gè)語句俩块,但是JavaScript 引擎 可不這么看。它將var a
個(gè)a = 2
看作兩個(gè)分離的語句浓领,第一個(gè)是編譯期的任務(wù)玉凯,而第二個(gè)是執(zhí)行時(shí)的任務(wù)。
這將導(dǎo)致在一個(gè)作用域內(nèi)的所有聲明联贩,不論它們出現(xiàn)在何處漫仆,都會(huì)在代碼本身被執(zhí)行前 首先 被處理。你可以將它可視化為聲明(變量與函數(shù))被“移動(dòng)”到它們各自的作用域頂部泪幌,這就是我們所說的“提升”盲厌。
聲明本身會(huì)被提升署照,但不是賦值,即便是函數(shù)表達(dá)式的賦值吗浩,也 不會(huì) 被提升建芙。
要小心重復(fù)聲明,特別是將一般的變量聲明和函數(shù)聲明混在一起 —— 如果你這么做的話懂扼,危險(xiǎn)就在眼前禁荸!