你不懂JS:作用域與閉包 第四章:提升

官方中文版原文鏈接

感謝社區(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)就在眼前禁荸!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阀湿,隨后出現(xiàn)的幾起案子赶熟,更是在濱河造成了極大的恐慌,老刑警劉巖陷嘴,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件映砖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灾挨,警方通過查閱死者的電腦和手機(jī)啊央,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涨醋,“玉大人瓜饥,你說我怎么就攤上這事≡÷睿” “怎么了乓土?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)溯警。 經(jīng)常有香客問我趣苏,道長(zhǎng),這世上最難降的妖魔是什么梯轻? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任食磕,我火速辦了婚禮,結(jié)果婚禮上喳挑,老公的妹妹穿的比我還像新娘彬伦。我一直安慰自己,他們只是感情好伊诵,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布单绑。 她就那樣靜靜地躺著,像睡著了一般曹宴。 火紅的嫁衣襯著肌膚如雪搂橙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天笛坦,我揣著相機(jī)與錄音区转,去河邊找鬼苔巨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛废离,可吹牛的內(nèi)容都是我干的侄泽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼厅缺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了宴偿?” 一聲冷哼從身側(cè)響起湘捎,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窄刘,沒想到半個(gè)月后窥妇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娩践,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年活翩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翻伺。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡材泄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吨岭,到底是詐尸還是另有隱情拉宗,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布辣辫,位于F島的核電站旦事,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏急灭。R本人自食惡果不足惜姐浮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望葬馋。 院中可真熱鬧卖鲤,春花似錦、人聲如沸畴嘶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掠廓。三九已至换怖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蟀瞧,已是汗流浹背沉颂。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工条摸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铸屉。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓钉蒲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親彻坛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顷啼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容