塊級(jí)作用域
為什么需要塊級(jí)作用域向瓷?
ES5 只有全局作用域和函數(shù)作用域业踢,沒有塊級(jí)作用域,這帶來很多不合理的場(chǎng)景嗽冒。
第一種場(chǎng)景呀伙,內(nèi)層變量可能會(huì)覆蓋外層變量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
因?yàn)関ar的變量提升添坊,導(dǎo)致內(nèi)層的tmp
變量覆蓋了外層的tmp
變量剿另。
第二種場(chǎng)景,用來計(jì)數(shù)的循環(huán)變量泄露為全局變量贬蛙。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代碼中雨女,變量i只用來控制循環(huán),但是循環(huán)結(jié)束后阳准,它并沒有消失氛堕,泄露成了全局變量。一般來說這并不是我們想要的溺职。
................................................................................................................
ES6 的塊級(jí)作用域
let實(shí)際上為 JavaScript 新增了塊級(jí)作用域岔擂。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面的函數(shù)有兩個(gè)代碼塊位喂,都聲明了變量n浪耘,運(yùn)行后輸出 5。這表示外層代碼塊不受內(nèi)層代碼塊的影響塑崖。如果兩次都使用var定義變量n七冲,最后輸出的值才是 10。
ES6 允許塊級(jí)作用域的任意嵌套规婆。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 報(bào)錯(cuò)
}}}};
上面代碼使用了一個(gè)五層的塊級(jí)作用域澜躺,每一層都是一個(gè)單獨(dú)的作用域蝉稳。第四層作用域無法讀取第五層作用域的內(nèi)部變量。
內(nèi)層作用域可以定義外層作用域的同名變量掘鄙。并不會(huì)相互污染耘戚。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
塊級(jí)作用域的出現(xiàn),實(shí)際上使得獲得廣泛應(yīng)用的立即執(zhí)行函數(shù)表達(dá)式(IIFE)不再必要了操漠。
// IIFE 寫法
(function () {
var tmp = ...;
...
}());
// 塊級(jí)作用域?qū)懛?{
let tmp = ...;
...
}
................................................................................................................
塊級(jí)作用域與函數(shù)聲明
函數(shù)能不能在塊級(jí)作用域之中聲明收津?這是一個(gè)相當(dāng)令人混淆的問題。
ES5 規(guī)定浊伙,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明撞秋,不能在塊級(jí)作用域聲明。
// 情況一
if (true) {
function f() {}
}
// 情況二
try {
function f() {}
} catch(e) {
// ...
}
上面兩種函數(shù)聲明嚣鄙,根據(jù) ES5 的規(guī)定都是非法的吻贿。
但是,瀏覽器沒有遵守這個(gè)規(guī)定哑子,為了兼容以前的舊代碼舅列,還是支持在塊級(jí)作用域之中聲明函數(shù),因此上面兩種情況實(shí)際都能運(yùn)行卧蜓,不會(huì)報(bào)錯(cuò)剧蹂。
ES6 引入了塊級(jí)作用域,明確允許在塊級(jí)作用域之中聲明函數(shù)烦却。ES6 規(guī)定宠叼,塊級(jí)作用域之中,函數(shù)聲明語句的行為類似于let其爵,在塊級(jí)作用域之外不可引用冒冬。
function f() { console.log('I am outside!'); }
(function () {
if (true) {
// 重復(fù)聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
上面代碼在 ES5 中運(yùn)行,會(huì)得到“I am inside!”摩渺,因?yàn)樵趇f內(nèi)聲明的函數(shù)f會(huì)被提升到函數(shù)頭部简烤,實(shí)際運(yùn)行的代碼如下。
// 瀏覽器的 ES6 環(huán)境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重復(fù)聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
上面的代碼在 ES6 瀏覽器中摇幻,都會(huì)報(bào)錯(cuò)横侦。
考慮到環(huán)境導(dǎo)致的行為差異太大,應(yīng)該避免在塊級(jí)作用域內(nèi)聲明函數(shù)绰姻。如果確實(shí)需要枉侧,也應(yīng)該寫成函數(shù)表達(dá)式,而不是函數(shù)聲明語句狂芋。
// 瀏覽器的 ES6 環(huán)境
// 塊級(jí)作用域內(nèi)部的函數(shù)聲明語句榨馁,建議不要使用
{
let a = 'secret';
function f() {
return a;
}
}
// 塊級(jí)作用域內(nèi)部,優(yōu)先使用函數(shù)表達(dá)式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外帜矾,還有一個(gè)需要注意的地方翼虫。ES6 的塊級(jí)作用域必須有大括號(hào)屑柔,如果沒有大括號(hào),JavaScript 引擎就認(rèn)為不存在塊級(jí)作用域珍剑。
// 第一種寫法掸宛,報(bào)錯(cuò)
if (true) let x = 1;
// 第二種寫法,不報(bào)錯(cuò)
if (true) {
let x = 1;
}
上面代碼中招拙,第一種寫法沒有大括號(hào)旁涤,所以不存在塊級(jí)作用域,而let只能出現(xiàn)在當(dāng)前作用域的頂層迫像,所以報(bào)錯(cuò)劈愚。第二種寫法有大括號(hào),所以塊級(jí)作用域成立闻妓。
函數(shù)聲明也是如此菌羽,嚴(yán)格模式下,函數(shù)只能聲明在當(dāng)前作用域的頂層由缆。
// 不報(bào)錯(cuò)
'use strict';
if (true) {
function f() {}
}
// 報(bào)錯(cuò)
'use strict';
if (true)
function f() {}}