JavaScript 變數:const、let、var 與作用域
變數是用來 存放資料的容器。有了它,程式才能把算出來的結果、使用者輸入的內容、選取到的 DOM 先收起來,後面再拿出來用。
在 JavaScript 的語法構成 中已經簡單提過變數,這篇把宣告方式、作用域與常見錯誤一次講清楚。
宣告與賦值
一行變數宣告由三個部分組成:
const name = 'Away';
// ↑ ↑
// 變數名稱 值| 部分 | 說明 |
|---|---|
const | 宣告用的關鍵字,決定這個變數的行為 |
name | 變數名稱,之後就用它來取用資料 |
= 'Away' | 賦值,把右邊的值存進變數 |
宣告與賦值也可以分開,但用 const 宣告時必須在當下就給值:
let count;
// undefined,宣告了但還沒給值
console.log(count);
count = 0;
// 0
console.log(count);const、let 與 var
| 關鍵字 | 可否重新賦值 | 作用域 | 是否會提升並可先使用 | 建議 |
|---|---|---|---|---|
const | 不可 | 區塊 | 否 | 預設使用這個 |
let | 可 | 區塊 | 否 | 值真的需要變動時 |
var | 可 | 函式 | 是,值為 undefined | 舊寫法,不建議再用 |
const title = 'JavaScript';
// 錯誤:Assignment to constant variable.
// title = 'jQuery';
let count = 0;
// 可以,count 變成 1
count = 1;為什麼預設用 const
把不會變動的值宣告成 const,等於在程式裡標註 這個值從頭到尾都一樣。日後閱讀時,一眼就能分辨哪些變數需要追蹤變化,也不會不小心覆寫。
注意
const 保證的是 變數不能被重新指向另一個值,並非內容不可改:
const user = { name: 'Away' };
// 可以,改的是物件的屬性
user.name = 'Ivy';
// 錯誤,這才是重新指向另一個值
// user = { name: 'Ivy' };要連內容一起鎖住,得用 Object.freeze(user)。
作用域
作用域就是 變數能被存取的範圍。const 與 let 屬於 區塊作用域,只在最近的一組 {} 內有效:
if (true) {
const msg = '區塊內';
console.log(msg);
}
// 錯誤:msg is not defined
// console.log(msg);var 則是 函式作用域,if、for 的大括號攔不住它:
if (true) {
var msg = '區塊內';
}
// 區塊內,仍然拿得到
console.log(msg);這正是 var 麻煩的地方:變數的生命週期比您以為的長,很容易在別處被意外覆寫。
提升與暫時性死區
var 宣告會被 提升(Hoisting)到函式最前面,所以先使用不會報錯,只是值為 undefined:
// undefined
console.log(count);
var count = 10;const 與 let 也會被提升,但在宣告之前不能存取,這段區間稱為 暫時性死區(Temporal Dead Zone):
// 錯誤:Cannot access 'total' before initialization
console.log(total);
const total = 10;小提醒
這個錯誤訊息很常見,意思其實很單純:宣告要寫在使用之前。把 const 那一行往上搬就好。
命名規則與慣例
語法上的規則:
- 可以使用英文字母、數字、底線
_與$。 - 不能以數字開頭,例如:
1st不合法。 - 不能使用 保留字,例如:
const、function、if。 - 區分大小寫,
name與Name是兩個不同的變數。
實務上的慣例:
// 小駝峰命名
const userName = 'Away';
// 布林值以 is、has 開頭
const isActive = true;
const hasTicket = false;
// DOM 元素加上前綴,一眼就知道它是節點
const elemBox = document.querySelector('#Box');
// 不會變動的設定值用全大寫加底線
const MAX_COUNT = 10;注意
別用 a、data1、temp 這種看不出用途的名稱。變數名稱是寫給 未來的自己 看的,多打幾個字換來的可讀性非常值得。
值與型別
變數本身不限定型別,同一個 let 變數可以先放數字再放字串(這就是 JavaScript 被稱為 弱型別 的原因之一):
let value = 10;
value = '改成字串也可以';型別大致分兩類,差別在於變數存的是 值本身 還是 參考:
| 分類 | 型別 | 變數存的是 |
|---|---|---|
| 基本型別 | number、string、boolean、undefined、null | 值本身 |
| 物件 | object、array、function | 指向該物件的參考 |
// 基本型別:複製的是值,兩者各自獨立
let a = 1;
let b = a;
b = 2;
// 1
console.log(a);
// 物件:複製的是參考,兩個變數指向同一個物件
const objA = { count: 1 };
const objB = objA;
objB.count = 2;
// 2
console.log(objA.count);小提醒
改了 B,A 竟然也變了幾乎都是這個原因。需要真的複製一份,可以用展開運算子 { ...objA },但它只複製第一層。
常見問題
const、let、var 該用哪一個?
預設用 const,只有真的需要重新賦值時才改用 let。var 的作用域是整個函式又會被提升,容易寫出難以追蹤的程式,新程式碼不建議再用。
用 const 宣告的物件為什麼還能改內容?
因為 const 限制的是變數不能被重新指向另一個值,不是內容不可變。物件與陣列的屬性、元素都還能修改,要凍結內容才需要 Object.freeze()。
什麼是作用域?
作用域是變數能被存取的範圍。const 與 let 是區塊作用域,只在最近的一組大括號內有效;var 是函式作用域,在整個函式內都存取得到。
為什麼會出現 Cannot access before initialization?
因為在 const 或 let 宣告之前就使用了該變數。這段區間稱為 暫時性死區,變數已存在但還不能存取,把宣告移到使用之前即可。
變數命名有什麼規則?
可用英文字母、數字、底線與 $,但不能以數字開頭,也不能使用保留字。慣例是小駝峰命名、用英文且語意完整,布林值常以 is 或 has 開頭。