# 【JavaScript 筆記】語法(資料型別、變數) - part 2
[TOC]
歡迎你點入本篇文章,本系列網頁程式設計,主要紀錄我個人自學的軌跡,另外也作為日後個人複習用。若你喜歡本篇文章,歡迎在文章底下點一顆愛心,或是追蹤我的個人公開頁~
---
## 資料型別(Data Type)

Image Source:https://www.geeksforgeeks.org/javascript/variables-datatypes-javascript/
JavaScript 中所有的值都屬於某種資料型別,型別分為兩大類:
1. Primitive(原始型別)的值儲存的是值本身。
2. Non-Primitive(非原始型別)儲存的是記憶體位址的參考(Reference)。
另外,JS 的語法也定義了兩個型別的值:
1. Literals(實字):一個固定值,就是常數的意思。
2. Variables(變數):隨時會變動的值。
### 用 `typeof` 查看型別
`typeof` 運算子可在 Console 中直接查看任何值的型別:

### 原始型別(Primitive)
#### 1. Number(數字)
JS 的 Number 型別同時涵蓋整數與浮點數,底層都是 64 位元雙精度浮點數(IEEE 754 標準),安全整數範圍是 $\pm (2^{53} - 1)$ 。
範例:
```javascript=
let age = 21 // 整數
let price = 99.9 // 浮點數
let neg = -50 // 負數
// 特殊數值
let inf = Infinity // 無限大 (例如 1/0)
let negInf = -Infinity // 負無限大
let notNum = NaN // Not a Number (例如 "abc" * 2)
console.log(0.1 + 0.2) // 0.30000000000000004 (浮點數精度問題)
console.log(typeof NaN) // "number"
```
`NaN` 是非數字,但 `typeof NaN` 卻回傳 `"number"`,這是 JS 著名的怪癖之一。
#### 2. BigInt(大整數)
Number 型別無法精確表示超過 $2^{53} - 1$ 的整數,此時就要用 BigInt,在數字後面加上 n 即可宣告。

從上圖測試結果可看到:
- `9007199254740991 + 2` 的結果會是 `9007199254740992`,應該要是 `9007199254740993` 才對,這代表已經超過 `Number` 的整數上限了。
- 也可看到 BigInt 輸出的結果會最後帶有一個 n:`9007199254740993n`。
- `console.log(10n + 5)` 代表 BigInt 跟 Number 是不能相加的。
#### 3. String(字串)
用來表示文字,可以用單引號、雙引號或反引號包起來。
範例:
```javascript=
let s1 = 'Hello' // 單引號
let s2 = "World" // 雙引號
let s3 = `哈囉,${s1}!` // 反引號 (樣板字串,可嵌入變數)
console.log(s3) // 哈囉,Hello!
console.log(typeof s1) // "string"
// 常用字串操作
let str = "JavaScript"
console.log(str.length) // 10 (字串長度)
console.log(str.toUpperCase()) // "JAVASCRIPT"
console.log(str.slice(0, 4)) // "Java" (擷取第0到第3個字)
console.log(str.includes("Script")) // true (是否包含某字串)
```
實際執行畫面:

#### 4. Boolean(布林值)
只有兩個可能的值:`true`(真)或 `false`(假),是所有判斷式與流程控制的核心。
範例:
```javascript=
let isLoggedIn = true
let isAdmin = false
console.log(typeof true) // "boolean"
if (isLoggedIn) {
console.log("歡迎回來!")
} else {
console.log("請先登入")
}
// 比較運算會產生 Boolean
console.log(10 > 5) // true
console.log(10 === 5) // false
```
實際執行畫面:

Falsy 值:以下這些值在布林判斷中都被視為 false:
```javascript=
Boolean(0) // false
Boolean("") // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean(false) // false
```
反之,以上六個以外的所有值,都是 truthy(被視為 true)。
#### 5. Null(空值)
null 在 JS 裡面是一種原始值,表示沒有值。
範例:
```javascript=
let user = null
console.log(user) // null
console.log(typeof null) // "object"
// 要正確判斷 null,必須用嚴格相等
console.log(user === null) // true
```
`typeof null` 回傳 `"object"` 是 JS 的歷史遺留 bug,實際上 `null` 是獨立的原始型別,不是物件。
#### 6. Undefined(未定義)
`undefined` 代表變數被宣告了,但還沒被賦值,是 JS 自動給予的預設值 。
範例:
```javascript=
let x // 宣告但未賦值
console.log(x) // undefined
function greet(name) {
console.log(name)
}
greet() // undefined(沒有傳入參數)
console.log(typeof undefined) // "undefined"
```
#### Null vs Undefined
| | null | undefined |
| ------ | ----------- | ----------- |
| 意思 | 刻意設為空 | 還沒被賦值 |
| 誰設定的 | 程式設計師 | JS 引擎 |
| 使用情境 | 主動清空一個變數 | 尚未初始化 |
| typeof | `"object"` | `"undefined"` |
#### 7. Symbol(符號)
Symbol 是 ES6 新增的型別,用來建立全域唯一的識別字,即使描述相同的兩個 Symbol 也不相等。
範例:
```javascript=
let id1 = Symbol("id")
let id2 = Symbol("id")
console.log(id1 === id2) // false, 這邊永遠都不相等
console.log(typeof id1) // "symbol"
// 主要用途: 當作物件的唯一屬性 key,避免命名衝突
let uniqueKey = Symbol("secretKey")
let obj = {
name: "LukeTseng",
[uniqueKey]: "這是隱藏屬性"
}
console.log(obj[uniqueKey]) // "這是隱藏屬性"
console.log(obj.name) // "LukeTseng"
```
Symbol 主要出現在設計框架、函式庫或需要嚴格避免 key 碰撞的場景。
### 非原始型別(Non-Primitive)
非原始型別又稱參考型別(Reference Type),變數儲存的不是值本身,而是指向記憶體中資料的位址。
#### 1. Object(物件)
物件是用來儲存一組相關資料與功能的容器,以鍵值對(key-value pair)的形式組織 。
```javascript=
// 建立物件
let student = {
name: "LukeTseng", // 字串
age: 19, // 數字
isActive: true, // 布林
greet: function() { // 函式(稱為方法 Method)
console.log(`我是 ${this.name}`)
}
}
// 存取屬性(兩種方式)
console.log(student.name) // "LukeTseng"(點記法)
console.log(student["age"]) // 21(括號記法)
student.greet() // 我是 LukeTseng
// 新增 / 修改 / 刪除屬性
student.city = "高雄" // 新增
student.age = 22 // 修改
delete student.isActive // 刪除
console.log(typeof student) // "object"
```
#### 2. Arrays(陣列)
陣列是有順序的資料集合,用中括號 `[]` 表示,每個元素用索引(Index)存取,從 0 開始計算。
```javascript=
// 建立陣列
let fruits = ["Apple", "Banana", "Mango"]
let scores = [95, 87, 72, 100]
let mixed = ["Luke", 21, true, null] // 可以混合不同型別
// 存取元素
console.log(fruits[0]) // "Apple"
console.log(fruits[2]) // "Mango"
console.log(fruits.length) // 3(陣列長度)
// 常用陣列方法
fruits.push("Grape") // 在末尾新增
fruits.pop() // 刪除末尾元素
fruits.unshift("Strawberry") // 在開頭新增
fruits.shift() // 刪除開頭元素
console.log(fruits.includes("Banana")) // true
console.log(fruits.indexOf("Mango")) // 2
console.log(typeof fruits) // "object", 陣列的 typeof 也是 object
Array.isArray(fruits) // true, 要判斷是否為陣列,用這個方法
```
#### 3. Function(函式)
函式是可以重複執行的程式碼區塊,可以被存進變數、當參數傳遞。
```javascript=
// 函式宣告
function add(a, b) {
return a + b
}
console.log(add(3, 5)) // 8
// 函式運算式(Expression)
let multiply = function(a, b) {
return a * b
}
console.log(multiply(3, 5)) // 15
// 箭頭函式(Arrow Function, ES6 新增, 最常用)
let divide = (a, b) => a / b
console.log(divide(10, 2)) // 5
// 函式可以存入陣列、物件,也可以當參數傳入另一個函式(稱為 Callback)
let greet = (name) => `哈囉,${name}!`
let names = ["LukeTseng", "Amy", "Bob"]
names.forEach(name => console.log(greet(name)))
// 哈囉,LukeTseng!
// 哈囉,Amy!
// 哈囉,Bob!
console.log(typeof greet) // "function"
```
### 原始型別(Primitive) vs 非原始型別(Non-Primitive)
```javascript=
// 原始型別: 複製值, 互不影響
let a = 10
let b = a
b = 99
console.log(a) // 10 (a 不受 b 影響)
// 非原始型別: 複製參考位址, 會互相影響
let obj1 = { name: "Luke" }
let obj2 = obj1 // obj2 指向同一個記憶體位址
obj2.name = "Amy"
console.log(obj1.name) // "Amy" (obj1 也被改了)
```
| 特性 | Primitive | Non-Primitive |
| ---- | -------------- | ------------- |
| 儲存的是 | 值本身 | 記憶體位址(參考) |
| 複製後 | 各自獨立 | 共用同一份資料 |
| 比較方式 | 比較值 | 比較記憶體位址 |
| 可變性 | 不可變(Immutable) | 可變(Mutable) |
## 變數(Variables)
變數(Variable)是用來替記憶體中某塊空間取名字的,方便用於儲存與重複使用資料。
在 JS 中,宣告變數有三種關鍵字:`var`、`let`、`const` 。
### 1. `var`(舊式,不推薦)
var 是 ES6(2015)以前唯一的宣告方式,但它有幾個令人困惑的特性,現代開發幾乎已棄用。
語法:
```javascript
var 變數名稱 = 值
```
#### 特性一:函式作用域(Function Scope)
`var` 的有效範圍是整個函式或全域,而不是 `{}` 區塊,表示在 `if`、`for` 等區塊內宣告的 `var`,外面也看得到:
```javascript=
if (true) {
var msg = "hi"
}
console.log(msg) // "hi"
```
用我們一般學過程式語言的知識來說,這樣存取應該是存取不到的,但卻可以。
#### 特性二:Hoisting(提升)
JS 引擎在執行前,會先把 `var` 的宣告移到最頂端(但不帶值),導致使用時機早於宣告卻不報錯,只會得到 `undefined`:
```javascript=
console.log(x) // undefined(沒有報錯, 但還沒有值)
var x = 10
console.log(x) // 10
```
實際上 JS 把上面的程式碼理解成:
```javascript=
var x // ← 宣告被提升到頂端
console.log(x) // → undefined
x = 10
```
實際的執行畫面:

#### 特性三:可重複宣告
```javascript=
var name = "LukeTseng"
var name = "Amy"
console.log(name) // "Amy"
```
實際的執行畫面:

### 2. `let`(推薦)
ES6 推出的 `let` 解決了 `var` 的大部分問題,是現在最常用的宣告方式。
語法:
```javascript
let 變數名稱 = 值
```
#### 特性一:區塊作用域(Block Scope)
有效範圍是 `{}` 包住的區塊,出了區塊就無法存取:
```javascript=
if (true) {
let msg = "hi"
console.log(msg)
}
console.log(msg) // ReferenceError: msg is not defined
```
實際執行畫面:

#### 特性二:有 Hoisting 但不初始化(TDZ)
`let` 也會被 Hoisting,但不會初始化為 `undefined`,在宣告前使用會直接報錯。
這段宣告前的禁區稱為 `Temporal Dead Zone`(TDZ,暫時性死區):
```javascript=
console.log(y) // ReferenceError: Cannot access 'y' before initialization
let y = 20
```
實際執行畫面:

#### 特性三:不能重複宣告,但可以重新賦值
```javascript=
let score = 80
// let score = 90 // SyntaxError: 不能重複宣告
score = 90 // 可以重新賦值
console.log(score) // 90
```
### 3. `const`(宣告常數)
`const` 用來宣告不會再被重新賦值的變數,宣告時必須立刻給值。
語法:
```javascript
const 變數名稱 = 值
```
範例:
```javascript=
const PI = 3.14159
// PI = 3 // TypeError: Assignment to constant variable.
// const AGE // SyntaxError: 必須立刻初始化
console.log(PI) // 3.14159
```
`const` 阻止的是重新賦值,不是值的內容被修改,如果 `const` 存的是物件或陣列,其內部的屬性仍然可以修改:
```javascript=
const user = { name: "Luke", age: 19 }
// user = {} // 不能重新賦值(整個替換)
user.age = 22 // 可以修改物件內部的屬性
user.city = "高雄" // 可以新增屬性
console.log(user) // { name: "Luke", age: 22, city: "高雄" }
const nums = [1, 2, 3]
nums.push(4) // 可以修改陣列內容
console.log(nums) // [1, 2, 3, 4]
```
實際執行畫面:

### `var` / `let` / `const` 比較
| 特性 | var | let | const |
| -------- | ----------------- | ----------- | ----------- |
| 作用域 | 函式作用域或全域 | 區塊作用域 | 區塊作用域 |
| Hoisting | 初始化為 undefined | TDZ,提早存取報錯 | TDZ,提早存取報錯 |
| 重複宣告 | ✓可以 | ✗不行 | ✗不行 |
| 重新賦值 | ✓可以 | ✓可以 | ✗不行 |
| 宣告時需給值 | 不需要 | 不需要 | 必須 |
| 現代推薦程度 | 不推薦 | 優先使用 | 優先使用 |
### 變數名稱規則(識別字)
識別字(Identifier)就是幫變數、函式取的名字,然後 JS 對於變數名稱有以下硬性規則:
- 只能包含:英文字母(a-z、A-Z)、數字(0-9)、底線(`_`)、錢字號(`$`)。
- 不能以數字開頭。
- 大小寫有差異(`name` 和 `Name` 是不同的變數)。
- 不能是關鍵字(如 `let`、`const`、`if`、`return` 等)。
以下是合法與非法的變數識別字範例:
```javascript=
// 合法
let userName = "LukeTseng"
let _private = 42
let $price = 99
let score2 = 100
// 非法
// let 2score = 100 // 數字開頭
// let user-name = "" // 含有連字號
// let my name = "" // 含有空格
// let var = 5 // 保留字
```
命名慣例(非強制,但業界共識):
```javascript=
// 變數、函式 → camelCase(小駝峰)
let firstName = "LukeTseng"
let totalScore = 100
function getUserName() {}
// 類別 → PascalCase(大駝峰)
class UserProfile {}
// 常數(固定不變的值)→ 全大寫 + 底線
const MAX_SIZE = 100
const API_KEY = "abc123"
```
### 一行多變數宣告
```javascript=
// 宣告多個變數, 同時賦值
let name = "Luke", age = 21, city = "高雄"
// 宣告多個但只部分賦值
let x, y, z
x = 10
y = 20
// z 此時為 undefined
// const 也可以,但每個都必須立刻賦值
const PI = 3.14, E = 2.71, PHI = 1.618
let firstName = "Luke",
lastName = "Tseng",
score = 95
console.log(`${firstName} ${lastName},分數:${score}`)
// Luke Tseng,分數:95
```
### JS 是弱型別的程式語言
一些編譯式語言,如 C、C++ 等,都是強型別的程式語言,在宣告變數時一定要指定資料型別,如 C++ 寫法:
```cpp=
int age = 21;
string name = "LukeTseng";
```
JavaScript 完全不需要,因為它是弱型別(Weakly Typed)+動態型別(Dynamically Typed)的語言:
- 動態型別:變數的型別在執行時自動判斷,不需事先宣告。
- 弱型別:不同型別的值可以自動轉換互相運算(隱式型別轉換)。
```javascript=
// 同一個變數,型別可以隨時改變
let x = 42 // Number
console.log(typeof x) // "number"
x = "Hello" // 改成 String,完全合法
console.log(typeof x) // "string"
x = true // 改成 Boolean
console.log(typeof x) // "boolean"
// 弱型別: 不同型別自動轉換
console.log("5" + 3) // "53"(數字被轉成字串,做字串拼接)
console.log("5" - 3) // 2 (字串被轉成數字,做減法)
console.log(true + 1) // 2 (true 被轉成 1)
console.log(false + 1) // 1 (false 被轉成 0)
```
## 總整理
JavaScript 中的值分為兩大類別,並且可以透過 `typeof` 運算子來檢查任何值的型別:
- Primitive(原始型別):變數直接儲存值本身,複製時各自獨立,互不影響(不可變)。
- Non-Primitive(非原始型別 / 參考型別):變數儲存的是記憶體位址,複製時會共用同一份資料,牽一髮動全身(可變)。
另外本篇提到的其他兩個名詞:
- Literals(實字):程式碼中寫死的固定值(常數)。
- Variables(變數):用來儲存值、隨時可變動的容器。
### 原始型別(Primitive Types)
- Number(數字):
- 涵蓋整數與浮點數(底層皆為 64 位元浮點數)。
- 安全整數範圍是 $\pm (2^{53} - 1)$。
- 包含特殊值 `Infinity` 與 `NaN`。
- 注意:`typeof NaN` 的結果為 `"number"`。
- BigInt(大整數):專門處理超過 $2^{53} - 1$ 的巨大整數,宣告時需在數字後方加上 n(例如 10n)。
- 無法直接與 Number 型別混合運算。
- String(字串):文字型別,使用單引號、雙引號或反引號包覆。
- 反引號(樣板字串)支援 `${}` 嵌入變數。
- Boolean(布林值):僅有 `true` 與 `false`,是流程控制的核心。
- 在 JS 中會被判定為 `false` 的 Falsy 值只有六個:
1. `0`
2. `""`(空字串)
3. `null`
4. `undefined`
5. `NaN`
6. `false`
- Null(空值):開發者刻意設定的空值。
- 注意:`typeof null` 回傳 `"object"` 是 JS 的歷史遺留 Bug。
- Undefined(未定義):變數已宣告但尚未賦值時,JS 引擎自動給予的預設狀態。
- Symbol(符號):ES6 新增,用來建立全域唯一的識別字,最常用於物件的唯一屬性 Key,避免命名衝突。
### 非原始型別(Non-Primitive Types)
- Object(物件):以鍵值對(key-value pair)組合而成的資料容器。
- 可透過點記法(`obj.key`)或括號記法(`obj["key"]`)存取與修改屬性。
- Array(陣列):有順序的資料集合,使用 `[]` 宣告,索引值從 0 開始。
- `typeof` 檢查陣列會回傳 `"object"`,需改用 `Array.isArray()` 來精確判斷。
- Function(函式):可重複執行的程式碼區塊。
- 分為函式宣告、運算式與 ES6 箭頭函式(`=>`)。
### 變數(Variables)宣告
JavaScript 提供三種宣告變數的方式,現代開發強烈建議棄用 `var`,優先使用 `let` 與 `const`。
| 特性 | var | let | const |
| -------- | ----------------- | ----------- | ----------- |
| 作用域 | 函式作用域或全域 | 區塊作用域 | 區塊作用域 |
| Hoisting | 初始化為 undefined | TDZ,提早存取報錯 | TDZ,提早存取報錯 |
| 重複宣告 | ✓可以 | ✗不行 | ✗不行 |
| 重新賦值 | ✓可以 | ✓可以 | ✗不行 |
| 宣告時需給值 | 不需要 | 不需要 | 必須 |
| 現代推薦程度 | 不推薦 | 優先使用 | 優先使用 |
關於 `const` 的重要觀念:`const` 拒絕的是重新賦值的動作,若 `const` 儲存的是物件或陣列(參考型別),其內部的屬性或元素依然可以被新增、修改或刪除。
### 變數命名規則與 JS 語言特性
- 命名規則:只能包含大小寫英文字母、數字、底線(`_`)與錢字號(`$`),不能以數字開頭、區分大小寫,且不可使用系統保留關鍵字。
- 業界命名慣例:
- 變數與函式使用小駝峰(camelCase)
- 類別使用大駝峰(PascalCase)
- 常數使用全大寫加底線(MAX_SIZE)。
- 動態弱型別特性:JavaScript 不需事先宣告資料型別(動態型別),且不同型別的值互相運算時,會自動進行隱式型別轉換(弱型別),例如 `"5" + 3` 會變成字串 `"53"`,而 `"5" - 3` 則會算出數字 `2`。
## 參考資料
[Variables and Datatypes in JavaScript - GeeksforGeeks](https://www.geeksforgeeks.org/javascript/variables-datatypes-javascript/)
[Data types](https://javascript.info/types)
[Rules for naming variables in JavaScript - DEV Community](https://dev.to/imashwani/rules-for-naming-variables-in-javascript-g54)
[Mastering JavaScript Naming Conventions: 10 Best Practices for Cleaner Code | Syncfusion Blogs](https://www.syncfusion.com/blogs/post/top-10-javascript-naming-convention)