ES6+ const
1. 前言
上一節(jié)我們學(xué)習(xí)了使用 let
取代 var
聲明變量,但是很多情況下,我們希望我們聲明的變量不能被修改。在 ES5 中不能直接聲明一個(gè)常量,如果想聲明一個(gè)不可修改的變量需要借助 defineProperty
方法。ES6 為了彌補(bǔ)這方面的缺失,新增了 const
語句用于聲明一個(gè)常量。本節(jié)我們還將學(xué)習(xí)到 let
、 const
、 var
的區(qū)別。
2. 語句使用
2.1 基本使用
const
的使用類似于 let
,不同的是 const
在聲明時(shí)必須初始化一個(gè)值,而且這個(gè)值不能被改變。
const PI = 3.1415; // 定義一個(gè)圓周率常量 PI
上面的代碼聲明了一個(gè)常量 PI,如果聲明時(shí)沒有初始化值時(shí),則會拋出異常。
const PI;
// Uncaught SyntaxError: Missing initializer in const declaration
const
語句聲明的是一個(gè)常量,并且這個(gè)常量是不能被更改的:
const PI = 3.1415; // 定義一個(gè)圓周率常量 PI
PI = 12 // Uncaught TypeError: Assignment to constant variable.
這里聲明了一個(gè)圓周率常量,我們知道圓周率是固定的不會被改變的,如果對 PI 重新賦值,則會拋出不能給常量分配變量的錯(cuò)誤。
但如果使用 const
聲明的變量是一個(gè)對象類型的話,我們可以改變對象里的值,這是因?yàn)?const
存的變量只是一個(gè)地址的引用,所以只要不改變引用的值,就不會報(bào)錯(cuò)。如下面的例子:
const obj = {};
obj.a = 12 // 12
const arr = [];
arr.push(12); // 12
arr = {}; // Uncaught TypeError: Assignment to constant variable.
使用 const
聲明了一個(gè)對象和一個(gè)數(shù)組,然后增加對象的屬性和增加數(shù)組的值都不會報(bào)錯(cuò),這是因?yàn)槲覀儧]有改變 obj 和 arr 的引用。如果對 arr 進(jìn)行重賦值,則會報(bào)不能給常量分配變量的錯(cuò)誤。
2.2 const 和 let 共有特性
由于 const
和 let
都是聲明變量使用的,所以他們的使用方法基本相同,下面總結(jié)一下它們共有的內(nèi)容,詳情參考上一節(jié)的 let
的使用:
- 不能進(jìn)行變量提升,在聲明前不能被使用,否則會拋出異常;
- 存在暫時(shí)性死區(qū),在塊中不能被重復(fù)聲明。
3. ES5 模擬實(shí)現(xiàn) const
在 ES6 之前是不能定義常量的,如果想定義常量的話,需要借助 ES5 中的 defineProperty
方法,這里我們寫個(gè)示例:
function setConst(key, value, obj) {
Object.defineProperty(window, key, {
get: function(){
return value;
},
set: function(){
console.error('Uncaught TypeError: Assignment to constant variable');
},
});
}
setConst('PI', 3.1415);
console.log(PI) // 3.1415
PI = 3; // Uncaught TypeError: Assignment to constant variable.
上面的代碼是一個(gè)定義常量的函數(shù),使用了 ES5 的 Object.defineProperty
方法,這個(gè)方法允許在一個(gè)對象上定義一個(gè)新的屬性,具體使用詳情可以參考 ES5 的相關(guān)文檔說明。這里我們會在 window 對象上添加屬性,也可以自己定義一個(gè)對象進(jìn)行添加,可以實(shí)現(xiàn)局部作用域的效果。通過向 setConst
方法中傳入指定的變量和值來聲明一個(gè)常量,這樣我們就在 ES5 中實(shí)現(xiàn)了常量的概念。由此可見,ES6 的 const
帶來的好處。
4. 場景實(shí)例
4.1 let 及 const 常見問題
在工作中經(jīng)常會遇到 var
、let
及 const
以下幾個(gè)問題:
- 什么是變量提升?
- 什么是暫時(shí)性死區(qū)?
var
、let
及const
區(qū)別?
這些問題在上面的講解中都有提到過,這里我們總結(jié)一下:
4.2 什么是變量提升?
變量還沒有被聲明,但是我們卻可以使用這個(gè)未被聲明的變量,這種情況就叫做提升,并且提升的是聲明。
console.log(a); // undefined
var a = 1
這個(gè)代碼其實(shí)可以寫出下面這樣的方式:
var a;
console.log(a); // undefined
a = 1
其實(shí)變量提升就是,把變量名統(tǒng)一地提升到作用域的頂部進(jìn)行率先定義,這也就是變量提升。不僅變量可以被提升,函數(shù)也可以被提升,并且函數(shù)的提升要優(yōu)于變量的提升,函數(shù)提升會把整個(gè)函數(shù)挪到作用域頂部。
4.3 什么是暫時(shí)性死區(qū)?
暫時(shí)性死區(qū)主要是針對 let 和 const 而言的,因?yàn)樗鼈儾淮嬖谧兞刻嵘?,所以在它們聲明變量之前是不能使用的,這個(gè)時(shí)候如果使用了就會報(bào)錯(cuò),這時(shí)候就形成了暫時(shí)性的死區(qū),也就是不能被引用。
{
console.log(name); // ReferenceError: name is not defined.
let num = 100;
}
定義前被引用則會拋出異常。
4.4 var、let 及 const 區(qū)別?
上面兩個(gè)問題解決了,再看它們的區(qū)別其實(shí)就是顯而易見的,主要從以下幾個(gè)方面來分析它們之間的區(qū)別:
- var 聲明的變量是全局作用域下的,會污染全局變量;let、const 可以和 {} 連用,產(chǎn)生塊作用域不會污染全局;
- var 存在提升,我們能在聲明之前使用。let、const 因?yàn)闀簳r(shí)性死區(qū)的原因,不能在聲明前使用;
- var 在同一作用域下,可以重復(fù)聲明變量;let、const 不能重復(fù)聲明變量,否則會報(bào)錯(cuò);
- let 和 const 的用法基本一致,但是 const 聲明的變量不能再次賦值。
4.5 實(shí)例
下面的實(shí)例主要考察作用域的問題,下面的代碼輸出的結(jié)果是什么?
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000*i);
}
// 3
// 3
// 3
代碼分析: 這里由于 setTimeout 是異步回調(diào)函數(shù),所以 for 循環(huán)運(yùn)行完后才會執(zhí)行 setTimeout 內(nèi)的調(diào)用棧。使用 var 聲明的變量是全局作用域的,循環(huán)完后 i 的值是 3,所以會間隔 1s 打印一個(gè) 3。想要 i 的值不被覆蓋,這時(shí)可以借助 let 的塊級作用域的特性,來解決這個(gè)問題:
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000 * i);
}
// 0
// 1
// 2
代碼分析: 這里循環(huán)的變量 i 是 let 聲明的,當(dāng)前的 i 只在本輪循環(huán)有效,所以每一次循環(huán)的 i 其實(shí)都是一個(gè)新的變量。你可能會問,如果每一輪循環(huán)的變量 i 都是重新聲明的,那它怎么知道上一輪循環(huán)的值,從而計(jì)算出本輪循環(huán)的值?這是因?yàn)樵?JavaScript 引擎內(nèi)部會記住上一輪循環(huán)的值,初始化本輪的變量 i 時(shí),就在上一輪循環(huán)的基礎(chǔ)上進(jìn)行計(jì)算。
另外,for 循環(huán)還有一個(gè)特別之處,就是設(shè)置循環(huán)變量的那部分是一個(gè)父作用域,而循環(huán)體內(nèi)部是一個(gè)單獨(dú)的子作用域。這樣每次定義的 i 都是局部作用域下的變量,所以在異步之后,i 的值是不會變的,所以依次打印 0 到 3 的結(jié)果。
5. 小結(jié)
本節(jié)我們學(xué)習(xí)了使用 const
來聲明一個(gè)常量,這里需要注意以下幾點(diǎn):
- 對于不可變的變量,盡可能地使用
const
來聲明變量,如果需要更改值的時(shí)候再用let
聲明; let
和const
都只作用于塊級作用域內(nèi);let
和const
聲明的變量,都不能變量提升,都存在暫存死區(qū);let
和const
聲明的變量不允許重復(fù)聲明,無論重復(fù)用var
或者其他聲明都不行。