ES6常用新特性學(xué)習(xí)1-let和const

1. 簡介

在ES6以前,變量的聲明都是使用var關(guān)鍵字,且會(huì)進(jìn)行變量聲明提升。另外,我們曾經(jīng)講過,JS中是沒有塊級(jí)作用域的,這一點(diǎn)也帶來了很多的不便。ES6 新增了let和var兩個(gè)關(guān)鍵字,用來聲明變量。下面我們就來看看他們的用法。

2. let

我們來看下面一段代碼。

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

當(dāng)變量a在函數(shù)f內(nèi)使用var聲明時(shí),在全局無法直接引用該變量。但是在全局函數(shù)內(nèi)用一對(duì)花括號(hào)包裹的區(qū)域中生命的變量b,卻可以在全局中直接引用。因?yàn)閷?duì)于JS來講,是沒有塊作用域的。這一點(diǎn)和JAVA等語言有著很大的不同,也帶來了很多不便。舉一個(gè)簡單的例子:

var i = 1;
... // 一堆其他的操作
for (var i = 0;i<3;i++) {
    console.log(i);  // 0 1 2
}
console.log(i);  // 3

在for循環(huán)內(nèi)部生命的變量i其實(shí)是無效的,因?yàn)樵谕瑐€(gè)作用域(此處是全局作用域)已經(jīng)聲明過變量i。此時(shí)var i = 3;中的聲明會(huì)被忽略,而只保留i =3;(這塊內(nèi)容可以參考我的文章JS入門難點(diǎn)解析3-作用域)。所以,在for循環(huán)結(jié)束以后,你滿心以為會(huì)輸出最開始在全局聲明賦值的var i = 1;時(shí),結(jié)果卻是被循環(huán)改變的結(jié)果3。這明顯不是我們希望的結(jié)果,那么js中能否也使用塊級(jí)作用域呢,我們生命的變量可否只在塊級(jí)作用域中生效呢?ES6給我們提供了let??聪旅娲a:

let i = 1;
... // 一堆其他的操作
for (let i = 0;i<3;i++) {
    console.log(i);  // 0 1 2
}
console.log(i);  // 1

將for循環(huán)中的var改成let,其聲明的變量i就只在循環(huán)內(nèi)生效了。是不是更加靈活方便了呢?當(dāng)然,let使用時(shí)有些需要注意的地方。

2.1 不存在變量提升

var命令會(huì)發(fā)生”變量提升“現(xiàn)象,即變量可以在聲明之前使用,值為undefined。(可以參考我的文章 JS入門難點(diǎn)解析2-JS的變量提升和函數(shù)提升)這種現(xiàn)象多多少少是有些奇怪的,按照一般的邏輯,變量應(yīng)該在聲明語句之后才可以使用。

為了糾正這種現(xiàn)象,let命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報(bào)錯(cuò)。

// var 的情況
console.log(foo); // 2
var foo = 2;

// let 的情況
console.log(bar); // Uncaught ReferenceError: bar is not defined
let bar = 2;

2.2 暫時(shí)性死區(qū)

我們之前講到過,let沒有變量提升,也就是說在let聲明一個(gè)變量之前對(duì)其引用會(huì)報(bào)錯(cuò)。這個(gè)很好理解。但如果此時(shí)該變量在塊作用域外部也被聲明了呢?是否此時(shí)的引用是對(duì)外部該變量的引用呢?
看下面這段代碼:

var tmp = 123;

if (true) {
  tmp = 'abc';  // Uncaught ReferenceError: tmp is not defined
  let tmp;
}

這里,tmp = 'abc';一句會(huì)報(bào)錯(cuò)。也就是說,let不僅不允許其聲明的變量在其聲明前被引用,還不允許其引用外部的同名變量,相當(dāng)?shù)匕缘?。其?shí),ES6 明確規(guī)定,如果區(qū)塊中存在let和const命令,這個(gè)區(qū)塊對(duì)這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會(huì)報(bào)錯(cuò)。

在代碼塊內(nèi),使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時(shí)性死區(qū)”(temporal dead zone,簡稱 TDZ)。

if (true) {
  // TDZ開始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ結(jié)束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

需要注意,TDZ有時(shí)會(huì)導(dǎo)致代碼出錯(cuò)。比如:
曾經(jīng)安全的typeof可能不在安全:

typeof y; // 未被let鎖定,輸出undefined
typeof x; // 被let鎖定,報(bào)ReferenceError
let x;

另外,有些TDZ導(dǎo)致的錯(cuò)誤會(huì)十分隱晦:

function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 報(bào)錯(cuò)

改成

function bar(x = 2, y = x) {
  return [x, y];
}
bar(); // [2, 2]

就okay了。還有如下情況:

// 不報(bào)錯(cuò)
var x = x;

// 報(bào)錯(cuò)
let x = x;
// ReferenceError: x is not defined

ES6 規(guī)定暫時(shí)性死區(qū)和let、const語句不出現(xiàn)變量提升,主要是為了減少運(yùn)行時(shí)錯(cuò)誤,防止在變量聲明前就使用這個(gè)變量,從而導(dǎo)致意料之外的行為。這樣的錯(cuò)誤在 ES5 是很常見的,現(xiàn)在有了這種規(guī)定,避免此類錯(cuò)誤就很容易了。

總之,暫時(shí)性死區(qū)的本質(zhì)就是,只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。

2.3 不允許重復(fù)聲明

let不允許在相同作用域內(nèi)(指的是ES5中規(guī)定的作用域,不包含塊級(jí)作用域)重復(fù)聲明一個(gè)變量,不管是使用var還是let。

var a = 1;
let a = 2;
console.log(a);  // Uncaught SyntaxError: Identifier 'a' has already been declared

以及

let b = 2;
var b = 1;
console.log(b);  // Uncaught SyntaxError: Identifier 'b' has already been declared

還有

let c = 1;
let c = 2;
console.log(c);  // Uncaught SyntaxError: Identifier 'c' has already been declared

另外看下邊兩組代碼。

let a = 1;
if (true) {
    var a = 2;
    console.log(a);  // Uncaught SyntaxError: Identifier 'a' has already been declared
}
let a = 1;
function f(){
    var a = 2;
    console.log(a);  // 2
}
f();

還有一點(diǎn),需要注意:
考慮到環(huán)境導(dǎo)致的行為差異太大,應(yīng)該避免在塊級(jí)作用域內(nèi)聲明函數(shù)。如果確實(shí)需要,也應(yīng)該寫成函數(shù)表達(dá)式,而不是函數(shù)聲明語句。

3. const

const的作用很簡單。const聲明一個(gè)只讀的常量。一旦聲明,常量的值就不能改變。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

在代碼中,我們將長會(huì)將一些常量用一些有實(shí)際意義的名稱去命名。比如上面代碼段中的圓周率PI。

const聲明的變量不得改變值,這意味著,const一旦聲明變量,就必須立即初始化,不能留到以后賦值。對(duì)于const來說,只聲明不賦值,就會(huì)報(bào)錯(cuò)。

const foo;
// SyntaxError: Missing initializer in const declaration

const的作用域與let命令相同:只在聲明所在的塊級(jí)作用域內(nèi)有效,其聲明的常量也是不提升,同樣存在暫時(shí)性死區(qū),只能在聲明的位置后面使用。

需要注意的是,const實(shí)際上保證的,并不是變量的值不得改動(dòng),而是變量指向的那個(gè)內(nèi)存地址不得改動(dòng)。對(duì)于簡單類型的數(shù)據(jù)(數(shù)值、字符串、布爾值),值就保存在變量指向的那個(gè)內(nèi)存地址,因此等同于常量。但對(duì)于復(fù)合類型的數(shù)據(jù)(主要是對(duì)象和數(shù)組),變量指向的內(nèi)存地址,保存的只是一個(gè)指針,const只能保證這個(gè)指針是固定的,至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的,就完全不能控制了。因此,將一個(gè)對(duì)象聲明為常量必須非常小心。
如下:

const a = [];
a.push('Hello'); // 可執(zhí)行
a.length = 0;    // 可執(zhí)行
a = ['Dave'];    // 報(bào)錯(cuò)

上面代碼中,常量a是一個(gè)數(shù)組,這個(gè)數(shù)組本身是可寫的,但是如果將另一個(gè)數(shù)組賦值給a,就會(huì)報(bào)錯(cuò)。

如果真的想將對(duì)象凍結(jié),應(yīng)該使用Object.freeze方法。(可以參考我的文章 JS入門難點(diǎn)解析13-屬性描述符,數(shù)據(jù)屬性和訪問器屬性)

const foo = Object.freeze({});

// 常規(guī)模式時(shí),下面一行不起作用;
// 嚴(yán)格模式時(shí),該行會(huì)報(bào)錯(cuò)
foo.prop = 123;

除了將對(duì)象本身凍結(jié),對(duì)象的屬性也應(yīng)該凍結(jié)。下面是一個(gè)將對(duì)象徹底凍結(jié)的函數(shù)。

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

參考

let 和 const 命令
深入ES6 (二)let和const
ES6這些就夠了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • let 和 const 命令 let 命令 塊級(jí)作用域 const 命令 頂層對(duì)象的屬性 gl...
    安小明閱讀 1,039評(píng)論 0 0
  • let 命令 塊級(jí)作用域 const 命令 頂層對(duì)象的屬性 global 對(duì)象 let 命令 基本用法 ES6 新...
    嘉奇呦_nice閱讀 1,694評(píng)論 0 2
  • let 基本用法 let命令,用來聲明變量。用法類似于var,但聲明的變量,只在let命令所在的代碼塊內(nèi)有效。 f...
    oWSQo閱讀 485評(píng)論 0 0
  • let 命令 塊級(jí)作用域 const 命令 頂層對(duì)象的屬性 global 對(duì)象 let 命令 基本用法 ES6 新...
    卞卞村長L閱讀 680評(píng)論 0 0
  • 1、let命令 作用域 let命令與var基本相似,只是let所聲明的變量只在let代碼塊內(nèi)有效。 因?yàn)樽兞渴褂胠...
    彩虹之夢閱讀 723評(píng)論 0 2

友情鏈接更多精彩內(nèi)容