ES6+ let
1. 前言
本節我們一起學習下 ES6 中的 let
,在 ES5 中變量的方法只有一個 var
,但是使用 var
來定義的變量存在很多缺陷和弊端,ES6 引入了 let
語句來聲明變量,同時引入了很多概念,比如塊級作用域、暫存死區等等。限制了任意聲明變量,提升了程序的健壯性。
2. 基本用法
let
的使用方法類似于 var
,并可以代替 var
來聲明變量。
{
let name = 'imooc';
}
let
允許你聲明一個作用域被限制在塊級中的變量、語句或者表達式。與 var
關鍵字不同的是,var
聲明的變量只能在全局或者整個函數塊中。 var
和 let
的不同之處在于 let
是在編譯時才初始化,也就是在同一個塊級下不能重復地聲明一個變量,否則會報錯。
let
不會在全局聲明時創建 window 對象的屬性,但是 var
會。
{
var name = 'imooc' // imooc
var name = 'iimooc' // iimooc
}
console.log(window.name) // iimooc
{
let age = 10 // 10
let age = 18 // Uncaught SyntaxError: Identifier 'age' has already been declared
}
console.log(window.age) // undefined
上面的代碼中,在一個塊中分別使用 var
和 let
來聲明變量對比他們之間的差異,從上面的代碼操作可以看出,我們可以使用 var
多次對 name 聲明,但是使用 let
聲明的 age,后面再使用 let
對其聲明是會報錯的。
var
是沒有塊的概念的,聲明的變量會是 window 對象上的屬性,在最外層的 window 上可以取到。而 let
存在塊的概念,不會添加到 window 對象上,這些是 let
和 var 之間的區別。從這里我們可以了解到為什么使用 let
。
3. 塊級作用域
在深入了解 let 前,我們需要了解一下,在 JavaScript 中有哪些作用域:
- 全局作用域
- 函數作用域 / 局部作用域
- 塊級作用域
上面是 JavaScript 中的三種作用域,那什么是作用域呢?首先要明白的是:幾乎所有的編程語言都存在在變量中儲值的能力,存儲完就需要使用這些值。所以,作用域就是一套規則,按照這套規則可以方便地去存儲和訪問變量。
在 ES5 中的作用域有全局作用域和函數作用域,而塊級作用域是 ES6 的概念。
3.1 全局作用域
全局作用域顧名思義,就是在任何地方都能訪問到它,在瀏覽器中能通過 window 對象拿到的變量就是全局作用域下聲明的變量。
var name = 'imooc';
console.log(window.name) // imooc
使用 var
定義的變量,可以在 window 對象上拿到此變量。這里的 name 就是全局作用域下的變量。
3.2 函數作用域
函數作用域就是在函數內部定義的變量,也就是局部作用域,在函數的外部是不能使用這個變量的,也就是對外是封閉的,從外層是無法直接訪問函數內部的作用域的。
function bar() {
var name = 'imooc';
}
console.log(name); // undefined
在函數內部定義的 name 變量,在函數外部是訪問不了的。要想在函數外部訪問函數內部的變量可以通過 return 的方式返回出來。
function bar(value) {
var name = ' imooc';
return value + name;
}
console.log(bar('hello')); // hello imooc
借助 return 執行函數 bar 可以取到函數內部的變量 name 的值進行使用。
3.3 塊級作用域
塊級作用域是 ES6 的概念,它的產生是要有一定的條件的,在大括號({}
)中,使用 let
或 const
聲明的變量,才會產生塊級作用域。
這里需要注意的是,塊級作用域的產生是 let
或 const
帶來的,而不是大括號,大括號的作用是限制 let
或 const
的作用域范圍。當不在大括號中聲明時, let
或 const
的作用域范圍是全局。
let name = 10;
console.log(window.name) // undefined
上面的代碼可以看到,使用 let
方式聲明的變量在 window 下是取不到的。
var num = 10;
{
var num = 20;
console.log(num) // 20
}
console.log(num) // 20
在使用 var
聲明的情況下,可以看出,外層的 num 會被 {} 中的 num 覆蓋,所以沒有塊級作用域的概念,下面看下使用 let
方式聲明:
let num = 10;
{
console.log(num); // Uncaught ReferenceError: Cannot access 'num' before initialization
let num = 20;
console.log(num) // 20
}
console.log(num) // 10
這里可以看出 {} 內外是互不干涉和影響的,如果在聲明 num 的前面進行打印的話,還會報錯,這個時候,num 處于暫存死區,是不能被使用的,下面我們會具體說明。
在低版本瀏覽器中不支持 ES6 語法,通常需要把 ES6 語法轉換成 ES5,使用 babel 把上面的代碼轉換后得到如下結果:
var num = 10;
{
console.log(_num); // num is not defined
var _num = 20;
console.log(_num); // 20
}
console.log(num); // 10
從上面的代碼中可以看出,雖然在 ES6 語法使用的是相同的變量名字,但是底層 JS 進行編譯時會認為他們是不同的變量。也就是說即使大括號中聲明的變量和外面的變量是相同的名字,但是在編譯過程它們是沒有關系的。
塊級作用域可以任意嵌套,如下實例:
{{
let x = 'Hello imooc'
{
console.log(x); // Hello imooc
let y = 'Hello World'
}
console.log(y); // 引用錯誤 ReferenceError: y is not defined.
}};
上方代碼每一層都是一個單獨的作用域,內層作用域可以讀取外層的變量,所以第一個會打印 Hello imooc
, 而外層無法讀取內層的變量,所以會報錯。
4. 不能變量提升
對應 var
我們知道可以變量提升的,提升到作用域的最頂部,作用域是全局,使得聲明變量前也可以使用,但值為 undefined
。
{
console.log(bar); // 輸出undefined,沒有值但不會報錯
var bar = 1;
}
一般變量都應該先聲明再使用,所以 let
和 const
語法規定必須聲明后使用,否則報錯。
{
console.log(name); // 引用錯誤 ReferenceError: name is not defined.
let name = 'imooc';
}
上面代碼中,都是在聲明前的時候使用變量的,這時候由于 let
不能進行變量提升所以會報引用錯誤。
5. 暫時性死區
上面講到在變量聲明前使用這個變量,就會報錯。在代碼塊內,使用 let
命令聲明變量之前,該變量都是不可用的。這在語法上,稱為 “暫時性死區”(temporal dead zone,簡稱 TDZ)。
{
console.log(name); // ReferenceError: name is not defined.
let name = 'imooc';
console.log(name); // imooc
}
上面代碼中,在塊中使用了 let
聲明了 name 變量,在使用前對 name 進行了聲明,name 則會處于暫存死區,不能被使用,如果引用則會引用錯誤。
Tips:注意對于
typeof
也是會報錯的。
{
console.log(typeof name); // Uncaught ReferenceError: Cannot access 'name' before initialization
let name = 'imooc';
}
上面的代碼中,name 引用錯誤:無法在初始化之前訪問 name,因為 name 在這個塊的下面進行了聲明,name 就是一個死區,不能被引用了。因此,typeof
運行時就會拋出一個 ReferenceError
的參數錯誤。
6. 重復聲明報錯
let
不允許在同一個函數或塊作用域中重復聲明同一個變量,否則會引起語法錯誤(SyntaxError)。
{
let x = 10;
let x = 11;
}
// Uncaught SyntaxError: Identifier 'x' has already been declared
在上面的代碼中報錯,所以,同一個變量名不可以在同一個作用域內重復聲明。
{
let x = 10;
var x = 1;
}
即使使用 var
去聲明也是不可以的,我們知道當使用 let
聲明的時候 x 已經是一個死區了,不可以被重復聲明了。
Tips:注意在
switch
語句中只有一個塊級作用域,所以下面這種情況也是會報錯的。
let x = 1;
switch(x) {
case 0:
let num;
break;
case 1:
let num;//重復聲明了
break;
}
// 報錯
如果把 case
后面的語句放到塊作用域中則不會報錯。
let x = 1;
switch(x) {
case 0: {//塊
let num;
break;
}
case 1: {//塊
let num;//這里就沒有關系了,可以正常聲明
break;
}
}
上方代碼,case
后面的語句 let
變量聲明在放到塊中,是單獨的作用域,所以就不會報錯。
7. 小結
本節講解了 let 語句的使用,還有作用域的概念,需要注意以下幾點:
let
只作用于塊級作用域內;let
聲明的變量不能進行變量提升,存在暫存死區;let
聲明的變量不允許重復聲明,無論重復用var
或者其他聲明都不行;- 盡量使用
let
去代替var
來聲明變量。