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