es6塊級作用域
一.var 宣告與變數提升機制
在JavaScript
中使用var
定義一個變數,無論是定義在全域性作用域函式函式的區域性作用域中,都會被提升到其作用域的頂部,這也是JavaScript
定義變數的一個令人困惑的地方。由於es5
沒有像其它類C
語言一樣的塊級作用域,因此es6
增加了let
定義變數,用來建立塊級作用域。
我們來看一個var定義變數的示例:
functionsetName(){ if(condition){ var name = 'loho'; console.log(name); }else{ console.log(name); } } var student = 'eveningwater'; setName();
以上程式碼可以理解成如下:
var student; function setName(){ var name; if(condition){ name = 'loho'; console.log(name);//loho }else{ console.log(name);//undefined } } student = 'eveningwater'; setName();
二.塊級宣告
塊級宣告意在指定一個塊級作用域,使得塊級作用域中所定義的變數無法再全域性被訪問到,塊級作用域也被稱為詞法作用域。
塊級作用域存在於兩個地方:
- 函式內部。
- 指定程式碼塊中。(即"{"和"}"之間的區域)
1.let 宣告
let
宣告同var
宣告用法一致,唯一的區別在於,let
宣告將變數限制在一個塊內,這樣就形成了一個塊級作用域,因此也就不會存在變數的提升了。
例如前面的示例,我們可以寫成如下:
let stundent = 'eveningwater'; function setName(){ if(condition){ let name = 'loho'; console.log(name);//loho }else{ //如果條件為false執行到這裡 console.log(name);//不返回值 } } setName();
2.禁止重宣告
在使用let
定義變數之前如果已經聲明瞭相同的變數,就會報錯。因此不能重複宣告變數。如以下示例:
var name = 'eveningwater'; //報錯,重複宣告 let name = 'loho';
當然這兩個變數必須是在同一個作用域中,如果是不同作用域中,則不會報錯。但有可能會遮蔽第一次宣告的變數。如以下示例:
var name = 'eveningwater'; if(condition){ //不會報錯 let name = 'loho'; }
3.const宣告
使用const
識別符號所宣告的變數必須要初始化,因此這個宣告的就是一個常量。如下例:
const name='eveningwater';//正確 const name;//錯誤,未初始化
const
宣告同let
宣告一樣,也是建立了一個塊級作用域,在這個塊級作用域之外是無法訪問到所宣告的變數的。換句話說,就是const
所宣告的變數不會有變數提升機制。如下例:
if(condition){ const name = 'eveningwater'; console.log(name);//'eveningwater' } //錯誤 console.log(name);
同樣的const
也不能重複宣告,如下例:
var name = 'eveningwater'; //錯誤,不能重複宣告 const name = 'loho';
但也可以在不同作用域中重複宣告,如下例:
var name = 'eveningwater'; if(condition){ const name = 'loho'; console.log(name);//loho,遮蔽全域性定義的變數 }
儘管const
宣告與let
宣告有太多相似的地方,但const
宣告也有一處與let
宣告不同,那就是const
宣告的變數不能被賦值,無論是在非嚴格模式下還是在嚴格模式下,都不能對const
宣告的變數進行賦值。如下例:
const name = 'eveningwater'; //錯誤 name = 'loho';
不過,如果定義的是一個物件,可以對物件的值進行修改,如下例:
const student = { name:'eveningwater' } student.name = 'loho';//沒問題 //錯誤,相當於賦值修改物件 student = { name:'loho' }
4.臨時死區
前面提到let
和const
宣告的變數都不會提升到作用域的頂部,因此在使用這兩個識別符號宣告之前訪問會報錯,即使是typeof
操作符也會觸發引用錯誤。如下例:
console.log(typeof name);//報錯 const name = 'eveningwater';
由於第一行程式碼就報錯了,因此後續的宣告變數語句不會執行,此時就出現了JavaScript
社群所謂的"臨時死區"(temporal dead zone)
.雖然這裡示例是const
宣告,但let
宣告也是一樣的。
但如果在const
或let
宣告的變數的作用域之外使用typeof
操作符監測卻不會報錯,只不過會返回undefined
。如下例:
console.log(typeof name);//undefined if(condition){ let name = 'eveningwater'; }
5.迴圈中的塊級作用域繫結
我們在使用var
宣告變數的時候,總會遇到這樣的情況,如下:
for(var i = 0;i < 100;i++){ //執行某些操作 } //這裡也能訪問到變數i console.log(i);//100
我們可以使用let
宣告將變數i
限制在迴圈中,此時再在迴圈作用域之外訪問變數i
就會報錯了,因為let
宣告已經為迴圈建立了一個塊級作用域。如下:
for(let i = 0;i < 100;i++){ //執行某些操作 } //報錯 console.log(i);
6.迴圈中的建立函式
在使用var
宣告變數的迴圈中,建立一個函式非常的困難,如下例:
var func = []; for(var i = 0;i < 5;i++){ func.push(function(){ console.log(i); }) } func.forEach(function(func){ func(); });
你可能預期想的是列印從0到5
之間,即0,1,2,3,4
的數字,但實際上答案並不是如此。由於函式有自己的作用域,因此在向陣列中新增函式的時候,實際上迴圈已經執行完成,因此每次列印變數i
的值都相當於是在全域性中訪問變數i
的值,即i = 5
這個值,因此實際上答案最終會返回5次5
.
在es5
中,我們可以使用函式表示式(IIFE)
來解決這個問題,因為函式表示式會建立一個自己的塊級作用域。如下:
var func = []; for(var i = 0;i < 5;i++){ (function(i){ func.push(function(){ console.log(i); }) })(i) } func.forEach(function(func){ func();//這就是你想要的答案,輸出0,1,2,3,4 })
;
但事實上有了es6
的let
宣告,我們不必如此麻煩,只需要將var
變成let
宣告就可以了,如下:
var func = []; for(let i = 0;i < 5;i++){ func.push(function(){ console.log(i); }) } func.forEach(function(func){ func();//輸出0,1,2,3,4 })
但是這裡不能使用const
宣告,因為前面提到過,const
宣告並初始化了一個常量之後是不能被修改的,只能在物件中被修改值。如以下示例就會報錯:
//在執行迴圈i++條件的時候就會報錯 for(const i = 0;i < len;i++){ console.log(i); }
因為i++
這個語句就是在嘗試修改常量i
的值,因此不能將const
宣告用在for
迴圈中,但可以將const
宣告用在for-in
或者for-of
迴圈中。如下:
var func = []; var obj = { name:'eveningwater', age:22 } for(let key in obj){ func.push(function(){ console.log(key) }) } func.forEach(function(func){ func();//name,age }); //以下也沒問題 var func = []; var obj = { name:'eveningwater', age:22 } for(const key in obj){ func.push(function(){ console.log(key) }) } func.forEach(function(func){ func();//name,age });
這裡並沒有修改key
的值,因此使用const
和let
宣告都可以,同理for-of
迴圈也是一樣的道理。for-of
迴圈是es6
的新增的循壞。。
7.全域性作用域繫結
let,const
宣告與var
宣告還有一個區別就是三者在全域性作用域中的行為。當使用var
宣告一個變數時,會在全域性作用域(通常情況下是瀏覽器window物件)中建立一個全域性屬性,這也就意味著可能會覆蓋window
物件中已經存在的一個全域性變數。如下例:
console.log(window.Array);//應該返回建立陣列的建構函式,即f Array(){} var Array = '這是陣列'; console.log(window.Array);//返回'這是陣列';
從上例,我們可以知道即使全域性作用域中已經定義了Array
變數或者已經存在了Array
屬性,但我們之後定義的Array
變數則會覆蓋之前已經定義好的或者已經存在的Array
變數(屬性)。
但是es6
的let
和const
宣告則不會出現這種情況,let
和const
宣告會建立一個新的繫結,也就是說不會成為window
物件的屬性。換句話說,就是所宣告的變數不會覆蓋全域性變數,而只會遮蔽它。如下例:
let Array = '這是陣列'; console.log(Array);//'這是陣列‘; console.log(window.Array);//應該返回建立陣列的建構函式,即f Array(){}
這也就是說window.Array !== Array
這個等式返回布林值true
。
8.塊級繫結的最佳實踐
在使用es6
塊級宣告變數中,最佳實踐是如果確定後續不會改變這個變數的值,用const
宣告,如果確定要改變這個變數的值,則用let
宣告。因為預料外的變數值的改變時很多bug
出現的源頭。如下示例:
functioneveningWater(){}; eveningWater.prototype.name = 'eveningwater'; let ew = new eveningWater(); //定義不能被修改的變數,也就是用於判斷例項型別的屬性 const _constructor = ew.constructor; //可以改變自定義的名字屬性 letname = ew.name; if(_constructor ===eveningWater || _constuctor === Object){ console.log(_constructor); }else{ name = 'loho'; console.log(name) }