【JavaScript 筆記】語法(資料型別、變數) - part 2

歡迎你點入本篇文章,本系列網頁程式設計,主要紀錄我個人自學的軌跡,另外也作為日後個人複習用。若你喜歡本篇文章,歡迎在文章底下點一顆愛心,或是追蹤我的個人公開頁~


資料型別(Data Type)

image

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 中直接查看任何值的型別:

image

原始型別(Primitive)

1. Number(數字)

JS 的 Number 型別同時涵蓋整數與浮點數,底層都是 64 位元雙精度浮點數(IEEE 754 標準),安全整數範圍是 ±(2531)\pm (2^{53} - 1)

範例:

1
2
3
4
5
6
7
8
9
10
11
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 型別無法精確表示超過 25312^{53} - 1 的整數,此時就要用 BigInt,在數字後面加上 n 即可宣告。

image

從上圖測試結果可看到:

  • 9007199254740991 + 2 的結果會是 9007199254740992,應該要是 9007199254740993 才對,這代表已經超過 Number 的整數上限了。
  • 也可看到 BigInt 輸出的結果會最後帶有一個 n:9007199254740993n
  • console.log(10n + 5) 代表 BigInt 跟 Number 是不能相加的。

3. String(字串)

用來表示文字,可以用單引號、雙引號或反引號包起來。

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 (是否包含某字串)

實際執行畫面:

image

4. Boolean(布林值)

只有兩個可能的值:true(真)或 false(假),是所有判斷式與流程控制的核心。

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

實際執行畫面:

image

Falsy 值:以下這些值在布林判斷中都被視為 false:

1
2
3
4
5
6
Boolean(0)          // false
Boolean("") // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean(false) // false

反之,以上六個以外的所有值,都是 truthy(被視為 true)。

5. Null(空值)

null 在 JS 裡面是一種原始值,表示沒有值。

範例:

1
2
3
4
5
6
7
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 自動給予的預設值 。

範例:

1
2
3
4
5
6
7
8
9
let x             // 宣告但未賦值
console.log(x) // undefined

function greet(name) {
console.log(name)
}
greet() // undefined(沒有傳入參數)

console.log(typeof undefined) // "undefined"

Null vs Undefined

nullundefined
意思刻意設為空還沒被賦值
誰設定的程式設計師JS 引擎
使用情境主動清空一個變數尚未初始化
typeof"object""undefined"

7. Symbol(符號)

Symbol 是 ES6 新增的型別,用來建立全域唯一的識別字,即使描述相同的兩個 Symbol 也不相等。

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)的形式組織 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 建立物件
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 開始計算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 建立陣列
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(函式)

函式是可以重複執行的程式碼區塊,可以被存進變數、當參數傳遞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 函式宣告
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)

1
2
3
4
5
6
7
8
9
10
11
// 原始型別: 複製值, 互不影響
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 也被改了)
特性PrimitiveNon-Primitive
儲存的是值本身記憶體位址(參考)
複製後各自獨立共用同一份資料
比較方式比較值比較記憶體位址
可變性不可變(Immutable)可變(Mutable)

變數(Variables)

變數(Variable)是用來替記憶體中某塊空間取名字的,方便用於儲存與重複使用資料。

在 JS 中,宣告變數有三種關鍵字:varletconst

1. var(舊式,不推薦)

var 是 ES6(2015)以前唯一的宣告方式,但它有幾個令人困惑的特性,現代開發幾乎已棄用。

語法:

1
var 變數名稱 = 值

特性一:函式作用域(Function Scope)

var 的有效範圍是整個函式或全域,而不是 {} 區塊,表示在 iffor 等區塊內宣告的 var,外面也看得到:

1
2
3
4
if (true) {
var msg = "hi"
}
console.log(msg) // "hi"

用我們一般學過程式語言的知識來說,這樣存取應該是存取不到的,但卻可以。

特性二:Hoisting(提升)

JS 引擎在執行前,會先把 var 的宣告移到最頂端(但不帶值),導致使用時機早於宣告卻不報錯,只會得到 undefined

1
2
3
console.log(x)  // undefined(沒有報錯, 但還沒有值)
var x = 10
console.log(x) // 10

實際上 JS 把上面的程式碼理解成:

1
2
3
var x          // ← 宣告被提升到頂端
console.log(x) // → undefined
x = 10

實際的執行畫面:

image

特性三:可重複宣告

1
2
3
var name = "LukeTseng"
var name = "Amy"
console.log(name) // "Amy"

實際的執行畫面:

image

2. let(推薦)

ES6 推出的 let 解決了 var 的大部分問題,是現在最常用的宣告方式。

語法:

1
let 變數名稱 = 值

特性一:區塊作用域(Block Scope)

有效範圍是 {} 包住的區塊,出了區塊就無法存取:

1
2
3
4
5
if (true) {
let msg = "hi"
console.log(msg)
}
console.log(msg) // ReferenceError: msg is not defined

實際執行畫面:

image

特性二:有 Hoisting 但不初始化(TDZ)

let 也會被 Hoisting,但不會初始化為 undefined,在宣告前使用會直接報錯。

這段宣告前的禁區稱為 Temporal Dead Zone(TDZ,暫時性死區):

1
2
console.log(y)  // ReferenceError: Cannot access 'y' before initialization
let y = 20

實際執行畫面:

image

特性三:不能重複宣告,但可以重新賦值

1
2
3
4
let score = 80
// let score = 90 // SyntaxError: 不能重複宣告
score = 90 // 可以重新賦值
console.log(score) // 90

3. const(宣告常數)

const 用來宣告不會再被重新賦值的變數,宣告時必須立刻給值。

語法:

1
const 變數名稱 = 值

範例:

1
2
3
4
5
const PI = 3.14159
// PI = 3 // TypeError: Assignment to constant variable.
// const AGE // SyntaxError: 必須立刻初始化

console.log(PI) // 3.14159

const 阻止的是重新賦值,不是值的內容被修改,如果 const 存的是物件或陣列,其內部的屬性仍然可以修改:

1
2
3
4
5
6
7
8
9
10
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]

實際執行畫面:

image

var / let / const 比較

特性varletconst
作用域函式作用域或全域區塊作用域區塊作用域
Hoisting初始化為 undefinedTDZ,提早存取報錯TDZ,提早存取報錯
重複宣告✓可以✗不行✗不行
重新賦值✓可以✓可以✗不行
宣告時需給值不需要不需要必須
現代推薦程度不推薦優先使用優先使用

變數名稱規則(識別字)

識別字(Identifier)就是幫變數、函式取的名字,然後 JS 對於變數名稱有以下硬性規則:

  • 只能包含:英文字母(a-z、A-Z)、數字(0-9)、底線(_)、錢字號($)。
  • 不能以數字開頭。
  • 大小寫有差異(nameName 是不同的變數)。
  • 不能是關鍵字(如 letconstifreturn 等)。

以下是合法與非法的變數識別字範例:

1
2
3
4
5
6
7
8
9
10
11
// 合法
let userName = "LukeTseng"
let _private = 42
let $price = 99
let score2 = 100

// 非法
// let 2score = 100 // 數字開頭
// let user-name = "" // 含有連字號
// let my name = "" // 含有空格
// let var = 5 // 保留字

命名慣例(非強制,但業界共識):

1
2
3
4
5
6
7
8
9
10
11
// 變數、函式 → camelCase(小駝峰)
let firstName = "LukeTseng"
let totalScore = 100
function getUserName() {}

// 類別 → PascalCase(大駝峰)
class UserProfile {}

// 常數(固定不變的值)→ 全大寫 + 底線
const MAX_SIZE = 100
const API_KEY = "abc123"

一行多變數宣告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 宣告多個變數, 同時賦值
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++ 寫法:

1
2
int age = 21;
string name = "LukeTseng";

JavaScript 完全不需要,因為它是弱型別(Weakly Typed)+動態型別(Dynamically Typed)的語言:

  • 動態型別:變數的型別在執行時自動判斷,不需事先宣告。
  • 弱型別:不同型別的值可以自動轉換互相運算(隱式型別轉換)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 同一個變數,型別可以隨時改變
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 位元浮點數)。
    • 安全整數範圍是 ±(2531)\pm (2^{53} - 1)
    • 包含特殊值 InfinityNaN
    • 注意:typeof NaN 的結果為 "number"
  • BigInt(大整數):專門處理超過 25312^{53} - 1 的巨大整數,宣告時需在數字後方加上 n(例如 10n)。
    • 無法直接與 Number 型別混合運算。
  • String(字串):文字型別,使用單引號、雙引號或反引號包覆。
    • 反引號(樣板字串)支援 ${} 嵌入變數。
  • Boolean(布林值):僅有 truefalse,是流程控制的核心。
    • 在 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,優先使用 letconst

特性varletconst
作用域函式作用域或全域區塊作用域區塊作用域
Hoisting初始化為 undefinedTDZ,提早存取報錯TDZ,提早存取報錯
重複宣告✓可以✗不行✗不行
重新賦值✓可以✓可以✗不行
宣告時需給值不需要不需要必須
現代推薦程度不推薦優先使用優先使用

關於 const 的重要觀念:const 拒絕的是重新賦值的動作,若 const 儲存的是物件或陣列(參考型別),其內部的屬性或元素依然可以被新增、修改或刪除。

變數命名規則與 JS 語言特性

  • 命名規則:只能包含大小寫英文字母、數字、底線(_)與錢字號($),不能以數字開頭、區分大小寫,且不可使用系統保留關鍵字。
  • 業界命名慣例:
    • 變數與函式使用小駝峰(camelCase)
    • 類別使用大駝峰(PascalCase)
    • 常數使用全大寫加底線(MAX_SIZE)。
  • 動態弱型別特性:JavaScript 不需事先宣告資料型別(動態型別),且不同型別的值互相運算時,會自動進行隱式型別轉換(弱型別),例如 "5" + 3 會變成字串 "53",而 "5" - 3 則會算出數字 2

參考資料

Variables and Datatypes in JavaScript - GeeksforGeeks

Data types

Rules for naming variables in JavaScript - DEV Community

Mastering JavaScript Naming Conventions: 10 Best Practices for Cleaner Code | Syncfusion Blogs