JavaScript 塊作用域

塊作用域

是一個(gè)用來(lái)對(duì)之前的最小授權(quán)原則進(jìn)行擴(kuò)展的工具蹂随,將代碼從在函數(shù)中隱藏信息擴(kuò)展為在塊中隱藏信息咱台。

【之前的代碼】:

for(var loop = 0; loop < 10; loop++) {
    console.log(loop);
}

【說(shuō)明】:我們?cè)?for 循環(huán)的頭部直接定義了變量 loop图焰,通常是因?yàn)橹幌朐?for 循環(huán)內(nèi)部的上下文中使用 loop盐碱,而忽略了 loop 實(shí)際上會(huì)被綁定在所處的作用域中榛了。如果能夠?qū)?loop 聲明在 for 循環(huán)內(nèi)部會(huì)是一個(gè)很有意義的事情,而現(xiàn)在只能依靠自覺(jué)性來(lái)防止自己沒(méi)在作用域其他地方意外地使用 loop 變量抢肛。也正是基于這個(gè)原因狼钮,ES6 推出了塊作用域的概念。

【塊作用域的用處】:變量的聲明應(yīng)該距離使用的地方越近越好捡絮,并最大限度地本地化熬芜。

with

with 可以創(chuàng)建一個(gè)詞法作用域,將對(duì)象的屬性作為聲明在當(dāng)前作用域的變量(函數(shù))锦援。換言之猛蔽,with 從對(duì)象中創(chuàng)建的作用域僅在 with 聲明中而非外部作用域中有效剥悟。不可不推薦使用 with 來(lái)實(shí)現(xiàn)塊作用域灵寺,在這里提出只是為了說(shuō)明 with 有這個(gè)功能。

try/catch

JavaScript 的 ES3 規(guī)范中規(guī)定 try/catch 的 catch 分句會(huì)創(chuàng)建一個(gè)塊作用域区岗,其中聲明的變量?jī)H在 catch 內(nèi)部有效略板。

【注意】:其中聲明的變量是指 catch(variable),括號(hào)內(nèi)的變量慈缔,而不是在 {} 中聲明的變量叮称。

【示例】:

try {
    undefined();
} catch(err) {
    var name = "spirit";
    console.log(err);
}
console.log(name); // spirit
console.log(err);

let

let 關(guān)鍵字可以將變量綁定到所在的任意作用域中(通常是 { ... } 內(nèi)部)。換句話說(shuō)藐鹤,let 為其聲明的變量隱式地劫持了所在的塊作用域瓤檐。

【示例】:

var foo = true;

if(foo) {
    let bar = foo * 2;
    bar = something(bar);
    console.log(bar);
}

console.log(bar); // ReferenceError

【注意】:用 let 將變量附加在一個(gè)已經(jīng)存在額塊作用域上的行為是隱式的。也就是說(shuō)娱节,在開(kāi)發(fā)和修改代碼的過(guò)程中挠蛉,如果沒(méi)有密切關(guān)注哪些塊作用域中有綁定的變量,并且習(xí)慣性地移動(dòng)這些塊或者將其包含在其他的塊中肄满,將會(huì)導(dǎo)致代碼變得混亂谴古。

【解決方案】:為塊作用域顯式地創(chuàng)建塊可以部分解決這個(gè)問(wèn)題,使變量的附屬關(guān)系變得更加清晰稠歉。

var foo = true;

if(foo) {
    { // 顯示的塊
        let bar = foo * 2;
        bar = something(bar);
        console.log(bar);
    }
}

console.log(bar); // ReferenceError

【解釋】:只要聲明是有效的掰担,在聲明中的任意位置都可以使用 {} 括號(hào)來(lái)為 let 創(chuàng)建一個(gè)用于綁定的塊。

【注意】:使用 let 進(jìn)行的聲明不會(huì)在塊作用域中進(jìn)行提升怒炸。聲明的代碼被執(zhí)行之前带饱,聲明并不存在。

{
    console.log(bar); // ReferenceError!
    let bar = 2;
}

1. 垃圾收集

另一個(gè)塊作用域非常有用的原因和閉包及回收內(nèi)存垃圾的回收機(jī)制相關(guān)阅羹。

【示例】:

function process(data) {
    // 在這里做點(diǎn)有趣的事情
}

var someReallyBigData = {
    // ...
};

process(someReallyBigData);

var btn = document.getElementById("my_button");

btn.addEventListener("click", function click(evt) {
    console.log("button clicked");
}, false);

【問(wèn)題】:click 函數(shù)的點(diǎn)擊回調(diào)并不需要 someReallyBigData 變量纠炮。理論上這意味著當(dāng) process() 執(zhí)行后月趟,在內(nèi)存中占用大量空間的數(shù)據(jù)結(jié)構(gòu)就可以被垃圾回收了。但是恢口,由于 click 函數(shù)形成了一個(gè)覆蓋整個(gè)作用域的閉包孝宗,JavaScript 引擎極有可能依然保存著這個(gè)結(jié)構(gòu)(取決于具體實(shí)現(xiàn))。

【示例】:

function process(data) {
    // 在這里做點(diǎn)有趣的事情
}

// 在這個(gè)塊中定義的內(nèi)容完事可以銷毀耕肩!
{
    let someReallyBigData = {
        // ...
    };
    
    process(someReallyBigData);   
}

var btn = document.getElementById("my_button");

btn.addEventListener("click", function click(evt) {
    console.log("button clicked");
}, false);

【解釋】:使用塊作用域可以讓引擎清楚地知道沒(méi)有必要繼續(xù)保存 someReallyBigData 因妇。

【建議】:為變量顯式聲明塊作用域,并對(duì)變量進(jìn)行本地化綁定是非常有用的工具猿诸,可以把它添加到你的代碼工具箱中了婚被。

2. let 循環(huán)

【示例】:

for(let loop = 0; loop < 10; loop++) {
    console.log(loop);
}

console.log(loop); // ReferenceError

// 等價(jià)于
{
    let j;
    for(j = 0; j < 10; j++) {
        let i = j; // 每個(gè)迭代重新綁定!
        console.log(i);
    }
}

【解釋】:for 循環(huán)頭部的 let 不僅將 loop 綁定到了 for 循環(huán)的塊中梳虽,事實(shí)上它將其重新綁定到了循環(huán)的每一個(gè)迭代中址芯,確保使用上一個(gè)循環(huán)迭代結(jié)束時(shí)的值重新進(jìn)行賦值。

【注意】:由于 let 聲明附屬于一個(gè)新的作用域而不是當(dāng)前的函數(shù)作用域(也不屬于全局作用域)窜觉,當(dāng)代碼中存在對(duì)于函數(shù)作用域中 var 聲明的隱式依賴時(shí)谷炸,就會(huì)有很多隱藏的陷阱,如果用 let 來(lái)替代 var 則需要在代碼重構(gòu)的過(guò)程中付出額外的精力禀挫。

const

除了 let 以外旬陡,ES6 還引入了 const,同樣可以用來(lái)創(chuàng)建塊作用域變量语婴,但其值是固定的(常量)描孟。之后任何視圖修改值的操作都會(huì)引起錯(cuò)誤。

小結(jié)

  • 塊作用域指的是變量和函數(shù)不僅可以屬于所處的作用域砰左,也可以屬于某個(gè)代碼塊(通常指 {} 內(nèi)部)匿醒。
  • 從 ES3 開(kāi)始,try/catch 結(jié)構(gòu)在 catch 分句中具有塊作用域缠导。
  • 在 ES6 中引入了 let 關(guān)鍵字廉羔,用來(lái)在任意代碼塊中聲明變量。
  • 有些人認(rèn)為塊作用域不應(yīng)該完全作為函數(shù)作用域的替代方案酬核。兩種功能應(yīng)該同時(shí)存在蜜另,開(kāi)發(fā)者可以并且也應(yīng)該根據(jù)需要選擇使用何種作用域,創(chuàng)造可讀嫡意、可維護(hù)的優(yōu)良代碼举瑰。

附錄:塊作用域的替代方案

從 ES3 發(fā)布以來(lái),JavaScript 中就有了塊作用域蔬螟,而 with 和 catch 分句就是塊作用域的兩個(gè)小例子此迅。

【示例】:

{
    let a = 2;
    console.log(a); // 2
}
console.log(a); // ReferenceError

【問(wèn)】:如何在 ES6 之前也可以實(shí)現(xiàn)上述示例的效果。

【答】:使用 catch。

try { throw 2; } catch(a) {
    console.log(a); // 2
}
console.log(a); // ReferenceError

【解釋】:利用 catch 分句具有塊作用域來(lái)實(shí)現(xiàn)耸序,除了丑陋且奇怪了一些忍些,還是挺好用的。

性能

  • 首先坎怪,try/catch 的性能的確很糟糕罢坝,但技術(shù)層面上沒(méi)有合理的理由來(lái)說(shuō)明 try/catch 必須這么慢,或者會(huì)一直慢下去搅窿。自從 TC39 支持在 ES6 的轉(zhuǎn)換器中使用 try/catch 后嘁酿,Traceur 團(tuán)隊(duì)已經(jīng)要求 Chrome 對(duì) try/catch 的性能進(jìn)行改進(jìn),他們顯然有很充分的動(dòng)機(jī)來(lái)做這件事情男应。
  • 其次闹司,IIFE 和 try/catch 并不是完全等價(jià)的,因?yàn)槿绻麑⒁欢未a中的任意一部分拿出來(lái)用函數(shù)進(jìn)行包裹沐飘,會(huì)改變這段代碼的含義游桩,其中的 this、return耐朴、break 和 contine 都會(huì)發(fā)生變化借卧。IIFE 并不是一個(gè)普適的解決方案,它只適合在某些情況下進(jìn)行手動(dòng)操作隔箍。
  • 最后問(wèn)題就變成了:你是否想要塊作用域谓娃?如果你想要脚乡,這些工具就可以幫助你蜒滩。如果不想要,繼續(xù)使用 var 來(lái)寫代碼就好了奶稠。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俯艰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锌订,更是在濱河造成了極大的恐慌竹握,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辆飘,死亡現(xiàn)場(chǎng)離奇詭異啦辐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蜈项,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門芹关,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人紧卒,你說(shuō)我怎么就攤上這事侥衬。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵轴总,是天一觀的道長(zhǎng)直颅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)怀樟,這世上最難降的妖魔是什么功偿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮往堡,結(jié)果婚禮上脖含,老公的妹妹穿的比我還像新娘。我一直安慰自己投蝉,他們只是感情好养葵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瘩缆,像睡著了一般关拒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庸娱,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天着绊,我揣著相機(jī)與錄音,去河邊找鬼熟尉。 笑死归露,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斤儿。 我是一名探鬼主播剧包,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼往果!你這毒婦竟也來(lái)了疆液?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤陕贮,失蹤者是張志新(化名)和其女友劉穎堕油,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體肮之,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掉缺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了戈擒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眶明。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖峦甩,靈堂內(nèi)的尸體忽然破棺而出赘来,到底是詐尸還是另有隱情现喳,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布犬辰,位于F島的核電站嗦篱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏幌缝。R本人自食惡果不足惜灸促,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涵卵。 院中可真熱鬧浴栽,春花似錦、人聲如沸轿偎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坏晦。三九已至萝玷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昆婿,已是汗流浹背球碉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仓蛆,地道東北人睁冬。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像看疙,于是被迫代替她去往敵國(guó)和親豆拨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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