為了使JavaScript語言可以用來編寫復(fù)雜的大型應(yīng)用程序,成為企業(yè)級開發(fā)語言,
ECMAScript 6.0(簡稱ES6)在標(biāo)準(zhǔn)中添加了很多新的特性。我們將用幾篇文章總結(jié)一下ES6標(biāo)準(zhǔn)中一些常用的新特性。本片文章主要講解ES6中的let、const命令,并區(qū)分其與var命令的區(qū)別。同時歡迎大家隨時指正錯誤、探討交流。
本文已同步至我的個人主頁。歡迎訪問查看更多內(nèi)容!謝謝大家的關(guān)注和支持!
let 與 var 的區(qū)別
一、let聲明的變量只在其所在的塊級作用于有效
所謂塊級作用域是指:將多個代碼語句封裝在一起,通常是包含在一個大括號中,沒有返回值。比如:
if (true) { // 塊級作用域 }
for (let i = 0; i < 10; i++) { // 塊級作用域 }
while (true) { // 塊級作用域 }
switch (case) { // 塊級作用域 }
以上例子,大括號({...})中形成的都屬于塊級作用域。
眾所周知,在ES6之前,JavaScript中只有全局作用域和局部(函數(shù))作用域,不存在塊級作用域。而且也只能使用關(guān)鍵字var來聲明變量。所以用var聲明的變量要么是屬于全局作用域的全局變量,要么就是屬于局部(函數(shù))作用域的局部變量。
在ES6標(biāo)準(zhǔn)中,添加了使用let聲明變量的方式。使用let聲明的變量只在塊級作用域中有效,在其外層作用域訪問時就會報錯。
if (true) {
// 這個用let聲明的變量a,只在當(dāng)前塊級作用域中有效
let a = 123;
// 這個用var聲明的變量b,在全局作用域中都有效
var b = '123';
console.log(a); // 123
console.log(b); // '123'
}
console.log(a); // 報錯 —— ReferenceError: a is not defined.
console.log(b); // '123'
上面的例子中,因?yàn)樽兞?code>a是使用let聲明的,它只在其所在的塊級作用域——if后面的大括號({...})之中有效,在塊級作用域外層訪問時就會報錯。而用var聲明的變量b,不受塊級作用域的約束,可以跨塊級作用域訪問。這個例子中,變量b實(shí)際是屬于全局作用域的全局變量。
那么,為什么ES6中需要引入塊級作用域的概念呢?為什么要增加使用let來聲明變量的方式呢?
因?yàn)?,如果沒有塊級作用域會導(dǎo)致一些不合理的情形出現(xiàn)。
1、 內(nèi)層變量可能會覆蓋外層變量。
var a = 'Global';
function inner() {
if (true) {
console.log(a); // undefined
var a = 'inner';
/**
* 以上兩行代碼相當(dāng)于
* var a;
* console.log(a);
* a = 'inner';
* 再次使用var聲明同名變量a,會覆蓋全局變量a
*/
}
}
inner();
這個例子,當(dāng)在函數(shù)inner內(nèi)部if代碼塊內(nèi)首先訪問變量a時,卻得到的是undefined。這是因?yàn)榫o隨其后var聲明的同名變量a會變量提升并覆蓋全局變量a。所以打印出a的值為undefined。
2、計數(shù)的循環(huán)變量會泄露為全局變量
for (var i = 0; i < 10; i++) {
// 一些循環(huán)操作
}
console.log(i); // 10
上面的例子,for循環(huán)中的循環(huán)變量按道理來說應(yīng)該只屬于for循環(huán)體,循環(huán)結(jié)束就不能再訪問。但實(shí)際這樣用var聲明的i,屬于外層作用域中的變量,也就是說i泄露為全局變量。所以當(dāng)執(zhí)行到console.log(i)時,因?yàn)?code>i經(jīng)過循環(huán)已經(jīng)增加到10,所以打印出i的值為10。
二、let聲明的變量不存在變量提升過程
用var聲明的變量,會在其作用域中發(fā)生變量提升的過程。變量會被提升到作用域頂部,JS默認(rèn)給變量一個undefined值。在使用var聲明一個變量前訪問它,得到的值永遠(yuǎn)是undefined。
但是,在ES6中使用let聲明的變量,不存在變量提升過程。也就是說,不能在使用let聲明任何一個變量前訪問它,否則都會報錯。
console.log(a); // 報錯——ReferenceError: a is not defined
let a = 'Hello World!';
三、let聲明的變量存在“暫時性死區(qū)”
只要使用let聲明了一個變量,那這個變量就“綁定”到了這個作用域(全局/局部/塊級),該變量就不再受外層作用域的影響。
ES6明確規(guī)定,如果區(qū)塊中存在let和const命令,這個區(qū)塊對這些命令聲明的變量從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
總之,在代碼塊內(nèi),使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區(qū)”(temporal dead zone,簡稱 TDZ)。
let g = 'Global';
if (true) {
g = 'Block'; // 報錯——ReferenceError: g is not defined
let g;
}
上面的例子中,if代碼塊最頂部一直到let聲明變量g之前,都是g的“暫時性死區(qū)”。在該范圍內(nèi)訪問g都會報錯。
四、let聲明的變量不允許再次重復(fù)聲明
使用var聲明變量,可以多次重復(fù)聲明一個同名變量。最終變量的值為最后一次聲明賦值的結(jié)果。
var a = 123;
var a = 'Hello World!';
console.log(a); // 'Hello World!'
但是,在同一作用域(全局/局部/塊級)中不允許使用let重復(fù)聲明變量?;蛘哒f不允許存在與用let聲明的變量同名的變量。以下代碼都會報錯!
// 先var,后let
var a = 123;
// ...一些代碼
let a = 'Hello World!'; // 報錯——Uncaught SyntaxError: Identifier 'a' has already been declared
// 先let,后var
let b = 123;
// ...一些代碼
var b = 'Hello World!'; // 報錯——Uncaught SyntaxError: Identifier 'a' has already been declared
// 先let,再let
let c = 123;
// ...一些代碼
let c = 'Hello World!'; // 報錯——Uncaught SyntaxError: Identifier 'a' has already been declared
五、let聲明的全局變量不會作為window對象的一個屬性
使用var聲明的全局變量,會被JS自動添加在全局對象window上,作為該對象的一個屬性。
var myVar = 'myName';
console.log(window.myVar); // 'myName'
console.log(window.hasOwnProperty('myVar')); // true
但是,使用let聲明的全局變量不會作為window對象的一個屬性。
let yourVar = 'yourName';
console.log(window.yourVar); // undefined
console.log(window.hasOwnProperty('yourVar')); // false
這個例子可以看出,let聲明的全局變量yourVar,并沒有被添加到window對象上,沒有作為window的一個屬性。
let 與const 的區(qū)別
在ES6中,上述所有let所具有的特性,對于const來說同樣存在。但const與let、var的區(qū)別在于const是用來聲明常量的。
常量具有以下特點(diǎn):
一、常量值不可修改
一個常量,一旦聲明,任何時間、任何地點(diǎn)都不能修改它的值。
const PI = 3.1415926;
console.log(PI); // 3.1415926
PI = 3; // 報錯——Uncaught TypeError: Assignment to constant variable.
二、常量在聲明時必須必須立即初始化(賦初始值)
不能只聲明一個常量名,但不對其進(jìn)行初始化賦值。否則在聲明常量時就會報錯。
const PI; // 報錯——Uncaught SyntaxError: Missing initializer in const declaration
PI = 3.1415926;
三、常量的值不可修改的實(shí)質(zhì)(重要?。。?/h4>
實(shí)際上,常量的值不變,是指常量指向的那個內(nèi)存地址中所保存的數(shù)據(jù)不可更改。對于簡單的數(shù)據(jù)類型(數(shù)值,字符串、布爾值),他們本身具體的值就保存在常量所指向的那個內(nèi)存地址中,所以不能修改改簡單類型的數(shù)據(jù)值。
但是,如果一個常量的值是一個引用類型值,那么常量所指向的內(nèi)存地址中實(shí)際保存的是指向該引用類型值的一個指針(也就是引用類型值在內(nèi)存中的地址)。所以const只能保證該引用類型地址不變,但該地址中的具體數(shù)據(jù)是可以變化的。
下面的例子,代碼不會報錯,可以正常運(yùn)行!
// !!!常量OBJ中實(shí)際保存的是后面的對象在內(nèi)存中的地址!!!
const OBJ = {};
/**
* !!!!!!!!!!
* 修改OBJ.prop1,實(shí)際只是修改了對象的屬性,
* 但并沒有改變該對象在內(nèi)存中的地址,
* 所以常量OBJ并沒有發(fā)生變化
* !!!!!!!!!!
*/
OBJ.prop1 = 123;
OBJ.prop2 = 'Hello World!'
/**
* !!!!!!!!!!
* 下面這一行就會報錯,
* 因?yàn)榇藭rOBJ指向了另一個對象,OBJ中保存的地址發(fā)生了變化
* !!!!!!!!!!
*/
OBJ = {}; // 報錯——Uncaught TypeError: Assignment to constant variable.
下面的例子和上面同理。
const ARR = [];
ARR.push('Hello'); // 可執(zhí)行
ARR.length = 0; // 可執(zhí)行
ARR = ['Dave']; // 報錯,因?yàn)锳RR重新指向了數(shù)組['Dave']所在的內(nèi)存地址