你不知道的JavaScript之變量聲明提升和閉包

聲明提升和閉包

提升

JavaScript 是一門解釋型語(yǔ)言鹿霸,原則上是不需要編譯的桨醋。但是它在代碼執(zhí)行之前會(huì)有一個(gè)編譯的流程重荠,這個(gè)流程發(fā)生在代碼執(zhí)行的前一刻。

示例:

console.log(a)    // undefined
var a = 2
console.log(a)    // 2

在這段代碼中晌端,按照常規(guī)邏輯,引擎通過(guò)作用域沒(méi)有找到恬砂,可能會(huì)拋出一個(gè)錯(cuò)誤 ReferenceError: a is not defined咧纠,但是這種情況并沒(méi)有發(fā)生。
實(shí)際上泻骤,在代碼運(yùn)行的前一刻漆羔,會(huì)執(zhí)行預(yù)編譯操作。當(dāng)遇到 var a = 2 這樣的語(yǔ)句時(shí)狱掂,會(huì)被拆分成兩部分演痒,var a(變量聲明)+ a = 2(變量賦值)。其中趋惨,變量聲明會(huì)發(fā)生在預(yù)編譯階段鸟顺,把 a 這個(gè)變量放到全局作用域中,沒(méi)有賦值器虾,則為 undefined讯嫂。
當(dāng)執(zhí)行第一個(gè)打印操作的時(shí)候,已經(jīng)完成了預(yù)編譯相關(guān)的處理兆沙,所以可以訪問(wèn)a欧芽,得到undefined;然后開(kāi)始執(zhí)行后面的賦值操作葛圃;等到第二個(gè)打印操作的時(shí)候已經(jīng)可以正常訪問(wèn) a 的值了渐裸。所以上述代碼也可以像下面這樣理解:

var a
console.log(a)
a = 2
console.log(2)

變量聲明提升,函數(shù)聲明整體提升

foo()    // foo
bar()    // TypeError: bar is not a function(此時(shí)bar為undefined)
function foo() {
  console.log(foo.name)
}
var bar = function () {
  console.log(bar.name)
}

變量聲明提升装悲,var 聲明的變量在預(yù)編譯的時(shí)候會(huì)被提升到當(dāng)前執(zhí)行環(huán)境的頂部昏鹃;
函數(shù)聲明整體提升,以函數(shù)聲明聲明函數(shù)的函數(shù)诀诊,會(huì)被整體提升到當(dāng)前執(zhí)行環(huán)境的頂部洞渤,所以執(zhí)行語(yǔ)句可以寫在函數(shù)聲明前面。函數(shù)表達(dá)式不可以属瓣,因?yàn)楹瘮?shù)表達(dá)式走的是變量聲明提升的規(guī)則载迄。

同一個(gè)變量既賦值給了變量,又作為函數(shù)聲明的標(biāo)識(shí)符

console.log(foo)    // function
var foo = 1
console.log(foo)    // 1
function foo() {
  console.log(foo.name)
}
console.log(foo)
/**
 * 這里常規(guī)思路是 function抡蛙,其實(shí)還是 1
 * 因?yàn)楹瘮?shù)聲明這段代碼已經(jīng)被整體提升到了當(dāng)前執(zhí)行環(huán)境的頂部护昧,已經(jīng)在前面執(zhí)行過(guò)了
 */ 

閉包

一個(gè)閉包的基本示例:

function foo() {
  let a = 3
  let tempFunc = function () {
    return a
  }
  return tempFunc
}
// 通過(guò) bar 標(biāo)識(shí)符引用了 foo 內(nèi)部的函數(shù) tempFunc
let bar = foo()
console.log(bar())

在這里,函數(shù) bar 的詞法作用域能夠訪問(wèn) foo 的內(nèi)部作用域粗截,這讓foo的內(nèi)部函數(shù)能夠在自己的詞法作用域外執(zhí)行惋耙,但是依然能夠訪問(wèn)自身詞法作用域的變量。

在foo()執(zhí)行后, 通常會(huì)期待foo()的整個(gè)內(nèi)部作用域都被銷毀绽榛, 因?yàn)槲覀冎酪嬗欣厥掌饔脕?lái)釋放不再使用的內(nèi)存空間湿酸。 由于看上去foo()的內(nèi)容不會(huì)再被使用, 所以很自然地會(huì)考慮對(duì)其進(jìn)行回收灭美。
而閉包的“ 神奇”之處正是可以阻止這件事情的發(fā)生推溃。 事實(shí)上內(nèi)部作用域依然存在, 因此沒(méi)有被回收届腐。誰(shuí)在使用這個(gè)內(nèi)部作用域铁坎?原來(lái)是bar()本身在使用。
拜bar()所聲明的位置所賜犁苏, 它擁有涵蓋foo()內(nèi)部作用域的閉包硬萍, 使得該作用域能夠一直存活,以供bar()在之后任何時(shí)間進(jìn)行引用傀顾。
bar()依然持有對(duì)該作用域的引用襟铭,而這個(gè)引用就叫作閉包碌奉。
這個(gè)函數(shù)在定義時(shí)的詞法作用域以外的地方被調(diào)用短曾。 閉包使得函數(shù)可以繼續(xù)訪問(wèn)定義時(shí)的詞法作用域。

基本示例的變種:

let bar
function foo() {
  let a = 2
  let tempFunc = function () {
    return a
  }
  bar = tempFunc
}
foo()
console.log(bar())

無(wú)論通過(guò)何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外赐劣, 它都會(huì)持有對(duì)原始定義作用域的引用嫉拐,無(wú)論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。

循環(huán)中的閉包

常見(jiàn)考題:

for (var i = 1; i <= 5; i ++) {
  setTimeout(function () {
    console.log(i)
  }, i*1000)
}
// 輸出什么魁兼?

正常情況下婉徘,我們對(duì)這段代碼行為的預(yù)期是分別輸出數(shù)字1~5,每秒一次咐汞,每次一個(gè)盖呼。
但實(shí)際上,這段代碼在運(yùn)行時(shí)會(huì)以每秒一次的頻率輸出五次6化撕。
首先解釋6是從哪里來(lái)的几晤。 這個(gè)循環(huán)的終止條件是i不再<=5。條件首次成立時(shí)i的值是6植阴。因此蟹瘾,輸出顯示的是循環(huán)結(jié)束時(shí)i的最終值。
仔細(xì)想一下掠手, 這好像又是顯而易見(jiàn)的憾朴, 延遲函數(shù)的回調(diào)會(huì)在循環(huán)結(jié)束時(shí)才執(zhí)行。 事實(shí)上喷鸽,當(dāng)定時(shí)器運(yùn)行時(shí)即使每個(gè)迭代中執(zhí)行的是setTimeout(.., 0)众雷,所有的回調(diào)函數(shù)依然是在循環(huán)結(jié)束后才會(huì)被執(zhí)行,因此會(huì)每次輸出一個(gè)6出來(lái)。

所以报腔,可以把上面的代碼株搔,轉(zhuǎn)換成下面的形式:

// 這里的 i 的作用域是在全局,不是預(yù)期的只有循環(huán)才能訪問(wèn)的
for (var i = 1; i <= 5; i ++) {}
setTimeout(function () {
  console.log(i)
}, 1*1000)
setTimeout(function () {
  console.log(i)
}, 2*1000)
setTimeout(function () {
  console.log(i)
}, 3*1000)
setTimeout(function () {
  console.log(i)
}, 4*1000)
setTimeout(function () {
  console.log(i)
}, 5*1000)
/**
 * 這里的循環(huán)是同步的纯蛾,而定時(shí)器里面的方法是異步調(diào)用的
 * 當(dāng)回調(diào)方法執(zhí)行的時(shí)候纤房,i 已經(jīng)變成 6 了
 */

1、使用IIFE函數(shù)改造使其符合預(yù)期

for (var i = 1; i <= 5; i ++) {
  (function (j) {
    setTimeout(function () {
      console.log(j)
    }, j*1000)
  }(i))
}
/**
 * 這里通過(guò)立即執(zhí)行函數(shù)給每次迭代都生成了一個(gè)新的作用域
 * 通過(guò)內(nèi)部聲明的變量j翻诉,把外部的i通過(guò)j傳入內(nèi)部作用域
 */

2炮姨、通過(guò)塊級(jí)作用域使其符合預(yù)期

for (let i = 1; i <= 5; i ++) {
  setTimeout(function () {
    console.log(i)
  }, i*1000)
}

此時(shí),變量i在循環(huán)過(guò)程中不止被聲明一次碰煌,每次迭代都會(huì)聲明舒岸。 隨后的每個(gè)迭代都會(huì)使用上一個(gè)迭代結(jié)束時(shí)的值來(lái)初始化這個(gè)變量。

一句話總結(jié)閉包:當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域芦圾, 即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行蛾派, 這時(shí)就產(chǎn)生了閉包。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末个少,一起剝皮案震驚了整個(gè)濱河市洪乍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夜焦,老刑警劉巖壳澳,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異茫经,居然都是意外死亡巷波,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門卸伞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)抹镊,“玉大人,你說(shuō)我怎么就攤上這事荤傲】宥” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵弃酌,是天一觀的道長(zhǎng)氨菇。 經(jīng)常有香客問(wèn)我,道長(zhǎng)妓湘,這世上最難降的妖魔是什么查蓉? 我笑而不...
    開(kāi)封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮榜贴,結(jié)果婚禮上豌研,老公的妹妹穿的比我還像新娘妹田。我一直安慰自己,他們只是感情好鹃共,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布鬼佣。 她就那樣靜靜地躺著,像睡著了一般霜浴。 火紅的嫁衣襯著肌膚如雪晶衷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天阴孟,我揣著相機(jī)與錄音晌纫,去河邊找鬼。 笑死永丝,一個(gè)胖子當(dāng)著我的面吹牛锹漱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播慕嚷,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼哥牍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了喝检?” 一聲冷哼從身側(cè)響起嗅辣,我...
    開(kāi)封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛇耀,沒(méi)想到半個(gè)月后辩诞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體坎弯,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纺涤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抠忘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撩炊。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖崎脉,靈堂內(nèi)的尸體忽然破棺而出拧咳,到底是詐尸還是另有隱情,我是刑警寧澤囚灼,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布骆膝,位于F島的核電站,受9級(jí)特大地震影響灶体,放射性物質(zhì)發(fā)生泄漏阅签。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一蝎抽、第九天 我趴在偏房一處隱蔽的房頂上張望政钟。 院中可真熱鬧,春花似錦、人聲如沸养交。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)碎连。三九已至灰羽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鱼辙,已是汗流浹背谦趣。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留座每,地道東北人前鹅。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像峭梳,于是被迫代替她去往敵國(guó)和親舰绘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348