Learning Objectives
- 用 let、const 宣告變數,說明 const 鎖住的是「指向哪個值」而不是物件內容
- 依識別字規則替變數命名,知道哪些名稱不能用
- 分辨 var 與 let 在作用範圍、重複宣告與提升上的差異
- 說明沒有宣告就指定值會發生什麼事,以及嚴格模式的作用
變數:替資料取一個名字
變數(variable)是一個名字,指向記憶體裡的一個值。程式用名字找到值,值可以是數字、字串、物件等任何資料。 JavaScript 有三種宣告方式:
| 宣告 | 能不能重新指定值 | 作用範圍 | 什麼時候用 |
|---|---|---|---|
const |
不能 | 區塊 | 預設用它:大部分的值宣告之後就不會再換 |
let |
可以 | 區塊 | 值之後會改變,例如計數器、迴圈變數 |
var |
可以 | 函式 | 舊寫法,新程式不要用;看懂舊程式碼即可 |
let 和 const 是 ES2015(也叫 ES6)加入的。在那之前只有 var,它有一些容易造成錯誤的特性,這一課後半段會一一看到。
現在的習慣是:先用 const,需要改值時才改成 let,不用 var。
JavaScript 是弱型別(動態型別)語言:宣告變數時不必寫型別,同一個 let 變數可以先放數字、之後再放字串。
三種宣告與重新指定
一步一步播放,看右邊的框架。注意第一步:程式還沒執行到第 4 行,var 宣告的 school 就已經在框架裡了,值是 undefined;let、const 宣告的變數要執行到那一行才出現。
const:不能重新指定,但物件內容可以改
const 有兩條規則:
- 宣告時一定要給值:
const PI;是語法錯誤(SyntaxError),程式一行都不會執行。 - 不能重新指定:
PI = 3;會得到TypeError: Assignment to constant variable.
但要注意,const 鎖住的是「這個名字指向哪一個值」。如果值是物件或陣列,物件裡面的內容仍然可以修改:
可以改屬性、可以 push 新元素,只是不能讓這個名字改指向另一個物件。
const 的物件與陣列
user 和 scores 都是 const,看第 3~7 行:右邊的物件內容一直在變,沒有出現任何錯誤。第 11 行想讓 scores 指向一個新陣列,這才是 const 不允許的事。
想一想:let 與 const
下面的程式會印出什麼?出錯時選錯誤的名稱。
const PI = 3.14;
PI = 3;
console.log(PI);
下面的程式會印出什麼?
const list = [1, 2];
list.push(3);
console.log(list);
下面的程式會印出什麼?出錯時選錯誤的名稱。
let total = 1;
let total = 2;
console.log(total);
識別字的規則
變數、函式、屬性的名字叫做識別字(identifier)。規則如下:
- 可以用英文字母、數字、
$、_,第一個字元不能是數字:score1、_count、$btn可以,1st不行 - 大小寫不同就是不同的名字:
score和Score是兩個變數 - 不能用保留字:例如
let、const、var、if、for、function、return、class、new、typeof、true、false、null - 不能有空白或連字號:
user name、user-name都不行(-會被當成減號) - 允許 Unicode 文字,所以
let 名字 = 'Amy';也能執行;但團隊合作時一般還是用英文命名
命名習慣:變數與函式用小駝峰(camelCase)如 totalPrice、isLoggedIn;不會改變的設定值常用全大寫加底線,如 MAX_SIZE。
重複宣告:var 可以重複宣告同一個名字(後面的會蓋掉前面的,容易不小心覆蓋別人的變數);let、const 重複宣告是語法錯誤。
沒有宣告就使用:
- 讀取一個從來沒宣告的變數:
ReferenceError: x is not defined - 沒宣告就直接指定值(
total = 100;):一般模式下不會出錯,而是在全域物件上建立一個屬性,等於偷偷產生全域變數 - 在程式或函式開頭寫
'use strict';進入嚴格模式(ES5 加入),沒宣告就指定值會直接出錯,可以及早發現打錯字 delete是用來刪除物件的屬性,不能刪除用var、let、const宣告的變數
沒有宣告就指定值:一般模式與嚴格模式
sloppy 函式裡的 total 沒有宣告。看第 6 行:函式已經結束,全域框架裡卻多了一個 total。strict 函式開頭寫了 'use strict',同樣的寫法就出錯了。
想一想:識別字與重複宣告
下列哪一個可以當作變數名稱?
下面的程式會印出什麼?
var a = 1;
var a = 2;
console.log(a);
下面的程式會印出什麼?
let Score = 1;
let score = 2;
console.log(Score + score);
var 與 let 的主要差異
var |
let |
|
|---|---|---|
| 作用範圍 | 函式作用域:在 if、for 的區塊裡宣告,整個函式都看得到 |
區塊作用域:只在宣告它的 { } 裡面有效 |
| 重複宣告 | 可以 | 不行(SyntaxError) |
| 重新指定值 | 可以 | 可以 |
| 宣告之前讀取 | 得到 undefined(被提升) |
ReferenceError(「尚未初始化」) |
提升(hoisting):JavaScript 執行一段程式之前,會先找出裡面所有的宣告。var 宣告的變數一開始就建立好,值是 undefined;
let、const 也會先被找出來,但在執行到宣告那一行之前不能使用,這段期間叫做暫時性死區(temporal dead zone)。
let 的設計讓「在宣告前就使用」變成明顯的錯誤,而不是悄悄得到 undefined。
下面兩個範例,第一個看提升,第二個看區塊作用域(作用域在 j12 會完整介紹)。
提升:var 是 undefined,let 是「尚未初始化」
第一步就看右邊:a 和 b 都已經在框架裡,a 是 undefined,b 標示「尚未初始化」。第 3 行讀 a 沒有問題;第 7 行在宣告前讀 b,就出現 ReferenceError。
區塊作用域:var 會跑出區塊,let 不會
這是投影片 Lab03 的例子。注意第 5 行:區塊裡的 let x 遮住了外面的 x,框架裡只看得到區塊裡的那一個;離開區塊(第 8 行)之後,外面的 x 又出現了。if 裡用 var 宣告的 leak,在 if 結束後還看得到。
想一想:提升與區塊
下面的程式會印出什麼?
console.log(n);
var n = 5;
下面的程式會印出什麼?出錯時選錯誤的名稱。
console.log(n);
let n = 5;
下面的程式會印出什麼?
function test() {
var x = 1;
{
let x = 2;
}
console.log(x);
}
test();
實作:選對 let 與 const
一杯飲料 50 元,價格不會變;杯數會改變。補上兩個宣告,並在中間把杯數改成 5(不要再宣告一次)。完成後應該印出 2 杯 100 和 5 杯 250。
重點精華回顧
- 宣告變數用
const(不會重新指定)或let(之後會改值);var是舊寫法,看得懂即可。 const宣告時一定要給值、不能重新指定;但const的物件與陣列,內容仍然可以修改。- 識別字可以用字母、數字、
$、_,不能以數字開頭、不能用保留字,大小寫不同就是不同的名字。 var可以重複宣告、是函式作用域、宣告前讀取得到undefined;let不能重複宣告、是區塊作用域、宣告前讀取是 ReferenceError。- 沒宣告就指定值會偷偷產生全域變數;用
'use strict'嚴格模式可以讓這種錯誤立刻現形。