EcmaScript 6 - 塊級作用域(block scope)

1. EcmaScript 5作用域

EcmaScript5的作用域有全局作用域(global scope)與函數作用域(function scope)兩種搜立。

1.1 全局作用域

在全局作用域中定義的變量夕吻,在整個上下文中都是可以訪問的蜻直。

var msg = 'Hello world';
console.log(msg); // Hello world
function sayHi(){
    console.log(msg);// Hello world
}
sayHi();

上面例子中msgsayHi()函數內外都可以訪問砾跃。
在NodeJs中盹牧,在js文件中直接使用var關鍵字聲明的變量加派,將會在模塊上聲明熟尉。
在瀏覽器中弊仪,在script標簽中直接使用var關鍵字聲明的變量熙卡,將定義在全局變量window上,window對象中的屬性擁有全局作用域励饵。

在嚴格模式(strict mode)下驳癌,變量在初始化前必須聲明,否則會拋出ReferenceError

'use strict';
a = 2;
console.log(a); // Uncaught ReferenceError: a is not defined

在非嚴格模式下曲横,使用未聲明的變量將隱式聲明為全局變量喂柒,定義在window上

a = 2;
console.log(a); // 2
console.log(a === window.a) // true

1.2 函數作用域

在函數作用域中定義的變量不瓶,只能在函數中被訪問

function fn(){
    var a = 1;
    console.log(a); // 1
}
console.log(a); // Uncaught ReferenceError: a is not defined

2. 聲明提升

聲明提升(hoisting)指通過var關鍵字聲明的變量,將提升到函數(或者全局作用域)的頂部進行聲明灾杰,與聲明語句的實際位置無關蚊丐。

function sayHi(condition){
    if(condition){
        var msg = 'Hello world';
        console.log(msg); // Hello world
    }else{
        console.log(msg); // undefined
    }
}

sayHi(true);
sayHi(false);

上面例子中msgif語句中進行了聲明和初始化賦值,但實際上msg將“提升”到函數頂部進行聲明艳吠,而賦值的位置不變麦备,還在if語句中。因此在else語句中同樣可以訪問msg變量昭娩,其值為undefined凛篙。Javascript引擎會將上面的代碼處理成類似下面的樣子。

function sayHi(condition){
    var msg栏渺;
    if(condition){
        msg = 'Hello world';
        console.log(msg); // Hello world
    }else{
        console.log(msg); // undefined
    }
}

sayHi(true);
sayHi(false);

3. 塊級作用域聲明

EcmaScript 6引入了塊級作用域(block scope)呛梆,塊級作用域只能在塊中被訪問,以下兩種情況可以創(chuàng)建塊級作用域的變量磕诊。

  • 在函數中
  • 在被{}包裹的塊中

3.1 let聲明

let關鍵字的作用類似var填物,用來聲明變量,不同的是其聲明的變量具有塊級作用域霎终。

'use strict';
function sayHi(condition){
    if(condition){
        let msg = 'Hello world';
        console.log(msg); // Hello world
    }else{
        console.log(msg); // ReferenceError: msg is not defined
    }
}

sayHi(true);
sayHi(false);

如上面例子滞磺,msg只能在if語句塊中被訪問,在else中訪問會產生ReferenceError錯誤

3.2 不能重復聲明

使用var關鍵字聲明的變量莱褒,在同一個作用域可以重復進行聲明击困,后面的將會覆蓋前面的。而使用let關鍵字聲明的變量广凸,在同一個作用域不能重復聲明(不管之前是用var阅茶,let, 或者const聲明)炮障,否則將會觸發(fā)SyntaxError錯誤目派。

'use strict';
var a = 1;
var a = 2;

//var b = 1;
//let b = 2; // SyntaxError: Identifier 'b' has already been declared

//let c = 1; 
//let c = 2; // SyntaxError: Identifier 'c' has already been declared

const d = 1;
let d = 2; // SyntaxError: Identifier 'd' has already been declared

在變量包含的作用域是用let關鍵字聲明的變量,不會生成錯誤胁赢。如下面代碼企蹭,在if語句中,新的變量a將覆蓋外面的變量a智末。

'use strict';
let a = 1;

if(true){
    let a = 2;
    console.log(a); // 2
}
console.log(a); // 1

3.3 const聲明

使用const關鍵字聲明的變量將作為常量使用谅摄,一旦被賦值,將不能再被改變系馆,因此const關鍵字聲明的變量必須同時進行初始化送漠,否則會拋出SyntaxError錯誤

'use strict';
const maxItems = 30;

const name; // SyntaxError: Unexpected token

上面例子中的name變量沒有進行初始化,因此將觸發(fā)SyntaxError由蘑。

const關鍵字與let關鍵字相同的地方

  • 聲明的變量具有塊級作用域闽寡。
  • 在相同的作用域代兵,重復聲明變量將會拋出SyntaxError錯誤

const關鍵字與let關鍵字不同的地方

  • const聲明的變量不能重復進行賦值, 否則將拋出TypeError錯誤

const聲明的對象不能改變爷狈,但是對象中的屬性可以進行改變植影。const綁定的是對象的引用,對象中實際的值是可以改變的涎永。

'use strict';
const person = {
    name:'Mary'
};

person.name = 'Jim';
person = {
    name:'Bary'
}; // TypeError: Assignment to constant variable

3.4 臨時死區(qū)

使用letconst聲明的變量思币,只有在聲明之后才能夠使用,否則會觸發(fā)ReferenceError錯誤羡微,即使在ES5中可以安全使用的typeof關鍵字在ES6中也不能保證可以安全使用谷饿。這種特殊行為就叫做臨時死區(qū)(Temporal Death Zone)。

'use strict';
if(true){
    console.log(typeof value); // ReferenceError: value is not defined

    const value = 'blue';
}

如上面代碼妈倔,由于臨時死區(qū)的存在博投,value變量在聲明之前是不能被訪問的。
Javascript引擎遇到一個代碼塊并且代碼塊中存在變量聲明启涯,那么要么進行變量聲明提升(hoisting)贬堵,將變量提升到函數或者全局作用域頂部進行聲明(使用var關鍵字);要么將變量放入臨時性死區(qū)(使用const或者let關鍵字)结洼,嘗試訪問臨時性死區(qū)中的變量將會導致ReferenceError錯誤。在遇到變量聲明(const或者let關鍵字)后叉跛,該變量將被移出臨時性死區(qū)松忍,變量就可以被訪問了。

'use strict';
console.log(typeof value); // undefined
if(true){
    let value = 'blue';
}

如上面代碼筷厘,value變量并未放入臨時性死區(qū)鸣峭,使用typeof關鍵字仍然是安全的。

4. 循環(huán)

4.1 循環(huán)的塊級作用域

實際開發(fā)中比較長使用的是在循環(huán)中使用塊級作用域酥艳,在Javascript中摊溶,var關鍵字在循環(huán)中使用有著很多不被其他語言開發(fā)者了解的缺陷。

for(var i=0;i<10;i++){
    // do something
}

console.log(i); // 10

例如上面代碼中充石,開發(fā)者希望達到的效果是i在循環(huán)之后不能訪問莫换,但是由于Javascript會進行變量聲明“提升”,i在循環(huán)結束之后骤铃,仍然能夠訪問拉岁。
使用let關鍵字,可以有效避免這個問題惰爬。

for(let i=0;i<10;i++){
    // do something
}

console.log(i); // ReferenceError

4.2 函數的循環(huán)

使用var關鍵字創(chuàng)建的變量在循環(huán)內部的函數中使用有一些問題喊暖。例如下面的代碼,開發(fā)者期待輸出從09撕瞧,但是由于i在循環(huán)之后仍然能夠訪問陵叽,funcs中的每個函數引用的是同一個i狞尔,因此在調用后輸出了十次10

var funcs = [];
for(var i = 0;i < 10;i++){
    funcs.push(function(){console.log(i);});
}

funcs.forEach(function(func){
    func(); // / outputs 10 ten times
});

上面的問題可以通過立即執(zhí)行函數(Immidiately Invoked Function Expressions巩掺,IIFEs)來解決

var funcs = [];
for(var i = 0;i < 10;i++){
    funcs.push((function(val){
        return function(){console.log(val);};
    })(i));
}

funcs.forEach(function(func){
    func(); // outputs 0, then 1, then 2, up to 9
});

如上面的代碼沪么,使用立即執(zhí)行函數強制將i作為參數傳入到每個函數中,這樣每個函數保存的是循環(huán)過程中i的副本锌半,因此可以正確輸出禽车。

4.3 在循環(huán)中使用let

使用let關鍵字可以用比較簡單清晰的代碼解決上面的問題。

'use strict';
var funcs = [];
for(let i = 0;i < 10;i++){
    funcs.push(function(){console.log(i);});
}

funcs.forEach(function(func){
    func(); // outputs 0, then 1, then 2, up to 9
});

如上面代碼刊殉,使用let會在每次循環(huán)過程中殉摔,創(chuàng)建一個新的變量i,因此每個函數讀取到的是每次循環(huán)的i的副本记焊,每個i的副本的值由循環(huán)初始化時i的值決定逸月。
for infor of循環(huán)中,let關鍵字有同樣的特性遍膜。如下面代碼碗硬,在每次循環(huán)時創(chuàng)建一個新key綁定,這樣每次循環(huán)都一個新的key變量瓢颅,因此每個函數輸出不同的key恩尾。如果使用var關鍵字代替let,那么所有函數將輸出相同的值c挽懦。

var funcs = [],
    object = {
        a: true,
        b: true,
        c: true
    };

for (let key in object) {
    funcs.push(function() {
        console.log(key);
    });
}

funcs.forEach(function(func) {
    func();     // outputs "a", then "b", then "c"
});

注意:let關鍵字在循環(huán)中的特性在規(guī)范中定義翰意,但是跟let關鍵字的“非提升”特性并不相關。實際上信柿,早起的一些let實現(xiàn)并沒有實現(xiàn)上述在循環(huán)中的特性冀偶。這些特性是逐漸添加的。

4.4 在循環(huán)中使用const

EcmaScript 6規(guī)范并沒有在循環(huán)中使用const給予禁止渔嚷,但是在不同的循環(huán)類型(for, for in, for of)进鸠,其行為是不同的。在for循環(huán)中形病,const可以在循環(huán)初始化時使用客年,但是如果嘗試修改其值,那么將會拋出錯誤窒朋。如下面代碼搀罢,i在初始化時時沒問題的,但是在調用i++時侥猩,將會發(fā)生TypeError錯誤榔至,因為++操作嘗試修改一個常量的值惶翻。

'use strict';

var funcs = [];
for(const i=0;i<10;i++){ // TypeError: Assignment to constant variable
    funcs.push(function(){
        console.log(i);
    });
}

for in, for of循環(huán)中使用const關鍵字則不會發(fā)生錯誤

'use strict';
var funcs = [],
    object = {
        a: true,
        b: true,
        c: true
    };

// doesn't cause an error
for (const key in object) {
    funcs.push(function() {
        console.log(key);
    });
}

funcs.forEach(function(func) {
    func();     // outputs "a", then "b", then "c"
});

如上面代碼粗井,使用const關鍵字與let關鍵字的不同之處只在于筝闹,key變量在塊中不能被修改奥邮。由于在for in, for of循環(huán)中,每次遍歷都會綁定一個新的key而不是嘗試修改原來的key枫弟,因此const關鍵字在for in, for of中使用是沒問題的邢享。

4.5 全局作用域

在全局作用域使用let或者const關鍵字與var是不同的。當在全局作用域使用var關鍵字時淡诗,創(chuàng)建一個新的全局變量骇塘,并將其綁定到全局對象的屬性上。因此韩容,可以使用var覆蓋全局對象上已經存在的變量款违。

// in a browser
var RegExp = "Hello!";
console.log(window.RegExp);     // "Hello!"

var ncz = "Hi!";
console.log(window.ncz);        // "Hi!"

上面的代碼中,window對象原有的RegExp被修改群凶,ncz被定義為一個全局變量插爹,并寫入全局對象的屬性上。
letconst關鍵字會創(chuàng)建在全局作用域創(chuàng)建一個變量请梢,但是并不會寫入到全局對象的屬性赠尾。因此,letconst關鍵字創(chuàng)建的變量不會覆蓋全局變量毅弧,而只能創(chuàng)建一個優(yōu)先讀取的“影子”變量气嫁。

// in a browser
let RegExp = "Hello!";
console.log(RegExp);                    // "Hello!"
console.log(window.RegExp === RegExp);  // false

const ncz = "Hi!";
console.log(ncz);                       // "Hi!"
console.log("ncz" in window);           // false

如上面代碼,RegExpwindow.RegExp是不同的形真,ncz也并不在window對象上杉编。因此,當開發(fā)者不需要在全局對象上創(chuàng)建變量咆霜,使用letconst關鍵字在全局作用域創(chuàng)建對象相比var是安全的。

5. 現(xiàn)有的最佳實踐

由于let關鍵字的特性更符合其他語言的習慣嘶朱,不會產生var關鍵字導致的各種問題蛾坯,因此盡量使用let關鍵字廣被開發(fā)者倡導。
由于大部分變量實際上不會改變疏遏,而修改變量會導致不可測的bug脉课,因此對于那些不會發(fā)生改變的變量,要使用const關鍵字

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末财异,一起剝皮案震驚了整個濱河市倘零,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戳寸,老刑警劉巖呈驶,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疫鹊,居然都是意外死亡袖瞻,警方通過查閱死者的電腦和手機司致,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聋迎,“玉大人脂矫,你說我怎么就攤上這事∶乖危” “怎么了庭再?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長牺堰。 經常有香客問我拄轻,道長,這世上最難降的妖魔是什么萌焰? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任哺眯,我火速辦了婚禮,結果婚禮上扒俯,老公的妹妹穿的比我還像新娘奶卓。我一直安慰自己,他們只是感情好撼玄,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布夺姑。 她就那樣靜靜地躺著掌猛,像睡著了一般盏浙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荔茬,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天慕蔚,我揣著相機與錄音孔飒,去河邊找鬼。 笑死桂对,一個胖子當著我的面吹牛蕉斜,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了杜顺?” 一聲冷哼從身側響起躬络,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤茴扁,失蹤者是張志新(化名)和其女友劉穎卖丸,沒想到半個月后坯苹,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡裳仆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年觉鼻,在試婚紗的時候發(fā)現(xiàn)自己被綠了萨惑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖行冰,靈堂內的尸體忽然破棺而出悼做,到底是詐尸還是另有隱情,我是刑警寧澤哗魂,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布肛走,位于F島的核電站,受9級特大地震影響录别,放射性物質發(fā)生泄漏朽色。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一组题、第九天 我趴在偏房一處隱蔽的房頂上張望葫男。 院中可真熱鬧,春花似錦崔列、人聲如沸梢褐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盈咳。三九已至耿眉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鱼响,已是汗流浹背鸣剪。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留热押,地道東北人西傀。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像桶癣,于是被迫代替她去往敵國和親拥褂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容