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