作用域(三)——函數(shù)中的作用域

對(duì)于前面提出的問題,最常見的答案是JavaScript具有基于函數(shù)的作用域娱挨,意味著每聲明一個(gè)函數(shù)都會(huì)為其自身創(chuàng)建一個(gè)氣泡余指,而其他結(jié)構(gòu)都不會(huì)創(chuàng)建作用域氣泡。但事實(shí)上這并不完全正確跷坝,下面我們來看一下酵镜。大家還可以關(guān)注我的微信公眾號(hào),蝸牛全棧柴钻。

首先需要研究一下函數(shù)作用域機(jī)器背后的一些內(nèi)容淮韭。

考慮下面的代碼:

function foo(a){
  var b = 2;
  function bar(){
  }
  var c = 3;
}

在這個(gè)代碼片段中,foo的作用域氣泡中包含了標(biāo)識(shí)符a贴届、b靠粪、c和bar【其中a在函數(shù)foo的參數(shù)內(nèi)】蜡吧。無論標(biāo)識(shí)符聲明出現(xiàn)在作用域中的何處,這個(gè)標(biāo)識(shí)符所代表的變量或函數(shù)都將附屬于所處作用域的氣泡占键。我們將在后續(xù)的文章討論具體的原理昔善。

Bar擁有自己的作用域氣泡。全局作用域也有自己的作用域氣泡捞慌,它只包含了一個(gè)標(biāo)識(shí)符:foo耀鸦。

由于標(biāo)識(shí)符a、b啸澡、c和bar都附屬于foo的作用域氣泡袖订,因此無法從foo的外部對(duì)他們進(jìn)行訪問。也就是說嗅虏,這些標(biāo)識(shí)符全都無法從全局作用域中進(jìn)行訪問洛姑,因此下面的代碼會(huì)導(dǎo)致ReferenceError錯(cuò)誤:

bar(); // 失敗
console.log(a, b, c); // 三個(gè)全都失敗

【調(diào)用bar的時(shí)候,因?yàn)樵谌肿饔糜蛑幸矝]有找到皮服,所以出現(xiàn)的錯(cuò)誤是ReferenceError,楞艾,而不是TypeError】

但是,這些標(biāo)識(shí)符(a龄广、b硫眯、c、foo和bar)在foo內(nèi)部都是可以被訪問的择同,同樣在bar內(nèi)部也可以被訪問(假設(shè)bar內(nèi)部沒有同名的標(biāo)識(shí)符聲明)【如果有同名的標(biāo)識(shí)符聲明两入,會(huì)出現(xiàn)之前說的遮蔽效應(yīng)】

函數(shù)作用域的含義是指,屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用(實(shí)際上在嵌套的作用域中也可以使用)敲才。這種設(shè)計(jì)方案是非常有用的裹纳,能充分利用JavaScript變量可以根據(jù)需要改變值類型的“動(dòng)態(tài)”特性。

但與此同時(shí)紧武,如果不細(xì)心處理那些可以在整個(gè)作用域范圍內(nèi)被訪問的變量剃氧,可能會(huì)帶來意想不到的問題。

隱藏內(nèi)部的實(shí)現(xiàn)

對(duì)函數(shù)的傳統(tǒng)認(rèn)知就是先聲明一個(gè)函數(shù)阻星,然后再向里面添加代碼朋鞍。但反過來想可以帶來一些啟示:從所寫的代碼中挑選任意的一個(gè)片段,然后用函數(shù)聲明對(duì)它進(jìn)行包裝妥箕,實(shí)際上就是把這些代碼“隱藏”起來了滥酥。

實(shí)際的結(jié)果就是在這個(gè)代碼片段的周圍創(chuàng)建了一個(gè)作用域氣泡,也就是說這段代碼中的任何聲明(變量或者函數(shù))都將綁定在這個(gè)新創(chuàng)建的包裝函數(shù)的作用域中矾踱,而不是先前所在的作用域中。換句話說疏哗,可以把變量和函數(shù)包裹在一個(gè)函數(shù)的作用域中呛讲,然后用這個(gè)作用域來“隱藏”他們禾怠。【就相當(dāng)于在原來的集體圈一個(gè)自己的小集體出來贝搁,只能一部分和外界溝通吗氏,至于怎么溝通,由這個(gè)小集體內(nèi)部自己決定】

為什么“隱藏”變量和函數(shù)是一個(gè)有用的技術(shù)雷逆?

有很多原因促成了這種基于作用域的隱藏方法弦讽。他們大都是從最小特權(quán)中引申出來的,也叫最小授權(quán)或最小暴露原則膀哲⊥【這個(gè)應(yīng)該是防止暴露的太多,會(huì)出現(xiàn)作用域的問題某宪,就像之前提到的with關(guān)鍵字用法實(shí)例】這個(gè)原則是指在軟件設(shè)計(jì)中仿村,應(yīng)該最小限度地暴露必要內(nèi)容,而將其他內(nèi)容都“隱藏”起來兴喂,比如某個(gè)模塊或?qū)ο蟮腁PI設(shè)計(jì)蔼囊。

這個(gè)原則可以延伸到如何選擇作用域來包含變量和函數(shù)。如果所有變量和函數(shù)都在全局作用域中衣迷,那當(dāng)然在所有的內(nèi)部嵌套作用域中訪問到他們畏鼓。但這樣會(huì)破壞前面提到的最小特權(quán)原則,因?yàn)榭赡軙?huì)暴露過多的變量或函數(shù)壶谒,而這些變量或函數(shù)本應(yīng)該是私有的云矫,正確的代碼應(yīng)該是可以阻止對(duì)這些變量或函數(shù)進(jìn)行訪問的。

例如

function doSomething(a){
  b = a + doSomethingElse(a * 2);
  console.log(b * 3);
}

function doSomethingElse(a){
  return a - 1;
}
var b;
doSomething(2); // 15

在這個(gè)代碼片段中佃迄,變量b和函數(shù)doSomethingElse應(yīng)該是doSomething內(nèi)容具體實(shí)現(xiàn)的“私有”內(nèi)容泼差。給予外部作用域?qū)和doSomethingElse的“訪問權(quán)限”不僅沒有必要,而且可能是“危險(xiǎn)”的呵俏,因?yàn)樗麄兛赡鼙挥幸饣驘o意地以非預(yù)期的方式使用堆缘,從而導(dǎo)致超出了doSomethingElse的適用條件。更“合理”的設(shè)計(jì)會(huì)將這些私有的具體內(nèi)容隱藏在doSomething內(nèi)部普碎,例如

function doSomething(a){
  function doSomethingElse(a){
    return a - 1;
  }
  var b;
  b = a + doSomethingElse(a * 2);
  console.log(b * 3);
}

doSomething(2); // 15

現(xiàn)在吼肥,b和doSomethingElse都無法從外部被訪問,而只能被doSomething控制麻车。功能性和最終效果都沒有受影響缀皱,但是設(shè)計(jì)上將具體內(nèi)容私有化了,設(shè)計(jì)良好的軟件都會(huì)以此進(jìn)行實(shí)現(xiàn)动猬∑《罚【這個(gè)在項(xiàng)目重構(gòu)上,也會(huì)有一席之地】

避免沖突

“隱藏”作用域中的變量和函數(shù)所帶來的另一個(gè)好處赁咙,是可以避免同名標(biāo)識(shí)符之間的沖突钮莲,【這個(gè)小編想到了同名的變量和函數(shù)】免钻,兩個(gè)標(biāo)識(shí)符可能具有相同的名字但用途卻不一樣,無意間可能造成命名沖突崔拥。沖突會(huì)導(dǎo)致變量值被意外覆蓋极舔。
例如:

function foo(){
  function bar(a){
    i = 3; // 修改for循環(huán)所屬作用域中的i
    console.log(a + i);
  }
  for(var I=0;i<10;i++){
    bar(I * 2); // 糟糕,無限循環(huán)了链瓦!
  }
}

foo();

Bar內(nèi)部的賦值表達(dá)式i=3外意外地覆蓋了聲明在foo內(nèi)部for循環(huán)中的i拆魏。在這個(gè)例子中將會(huì)導(dǎo)致無限循環(huán),因?yàn)閕被固定設(shè)置為3慈俯,永遠(yuǎn)滿足小于10這個(gè)條件渤刃。

Bar內(nèi)部的賦值操作需要聲明一個(gè)本地變量來使用,采用任何名字都可以肥卡,var i=3溪掀;就可以滿足這個(gè)需求(同時(shí)會(huì)為i聲明一個(gè)前面提到過的“遮蔽變量”)。另外一種方法是采用一個(gè)完全不同的標(biāo)識(shí)符名稱步鉴,比如var j=3;揪胃。但是軟件設(shè)計(jì)在某種情況下可能自然而然地要求使用同樣的標(biāo)識(shí)符名稱,因此在這種情況下使用作用域來“隱藏”內(nèi)部聲明是唯一的最佳選擇氛琢『暗荩【一定程度上也為重構(gòu)提供了更多要注意的事項(xiàng)和方案】

全局命名空間

變量沖突的一個(gè)典型例子存在于全局作用域中。當(dāng)程序中加載了多個(gè)第三方庫時(shí)阳似,如果他們沒有妥善地將內(nèi)部私有的函數(shù)或變量隱藏起來骚勘,就會(huì)很容易引發(fā)沖突。

這些庫通常會(huì)在全局作用域中聲明一個(gè)名字足夠獨(dú)特的變量撮奏,通常是一個(gè)對(duì)象俏讹,這個(gè)對(duì)象被用作庫的命名空間,所有需要暴露給外界的功能都會(huì)成為這個(gè)對(duì)象(命名空間)的屬性畜吊,而不是將自己的標(biāo)識(shí)符暴露在頂級(jí)的詞法作用域中泽疆。

例如:

var MyReallyCoolLibrary = {
  awesome: ’stuff’,
  doSomething: function(){
  },
  doAnotherThing: function(){
  }
}

模塊管理

另外一種避免沖突的辦法和現(xiàn)代的模塊機(jī)制很接近,就是從眾多模塊管理器中挑選一個(gè)來使用玲献。使用這些工具殉疼,任何庫都無需將標(biāo)識(shí)符加入到全局作用域中,而是通過依賴管理器的機(jī)制將庫的標(biāo)識(shí)符顯式地引入到另外一個(gè)特定的作用域中捌年。

顯而易見瓢娜,這些工具并沒有能夠違反詞法作用域規(guī)則的“神奇”功能。它們只能利用作用域的規(guī)則強(qiáng)制所有標(biāo)識(shí)符都不能注入到共享作用域中礼预,而是保持在私有眠砾、無沖突的作用域中,這樣可以有效規(guī)避掉所有的意外沖突托酸“保【就相當(dāng)于每個(gè)模塊都在自己的小盒子里伙单,大家互不干擾】

因此,只要你愿意哈肖,即使不適用任何依賴管理工具也可以實(shí)現(xiàn)相同的功效。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末念秧,一起剝皮案震驚了整個(gè)濱河市淤井,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摊趾,老刑警劉巖币狠,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異砾层,居然都是意外死亡漩绵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門肛炮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來止吐,“玉大人,你說我怎么就攤上這事侨糟“樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵秕重,是天一觀的道長不同。 經(jīng)常有香客問我,道長溶耘,這世上最難降的妖魔是什么二拐? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮凳兵,結(jié)果婚禮上百新,老公的妹妹穿的比我還像新娘。我一直安慰自己留荔,他們只是感情好吟孙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聚蝶,像睡著了一般杰妓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碘勉,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天巷挥,我揣著相機(jī)與錄音,去河邊找鬼验靡。 笑死倍宾,一個(gè)胖子當(dāng)著我的面吹牛雏节,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播高职,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼钩乍,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了怔锌?” 一聲冷哼從身側(cè)響起寥粹,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎埃元,沒想到半個(gè)月后涝涤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岛杀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年阔拳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片类嗤。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糊肠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遗锣,到底是詐尸還是另有隱情罪针,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布黄伊,位于F島的核電站泪酱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏还最。R本人自食惡果不足惜墓阀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拓轻。 院中可真熱鬧斯撮,春花似錦、人聲如沸扶叉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枣氧。三九已至溢十,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間达吞,已是汗流浹背张弛。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吞鸭。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓寺董,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刻剥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遮咖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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