# 【JavaScript 筆記】語法(資料型別、變數) - part 2 [TOC] 歡迎你點入本篇文章,本系列網頁程式設計,主要紀錄我個人自學的軌跡,另外也作為日後個人複習用。若你喜歡本篇文章,歡迎在文章底下點一顆愛心,或是追蹤我的個人公開頁~ --- ## 資料型別(Data Type) ![image](https://hackmd.io/_uploads/B1i0-laoZl.png) 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](https://hackmd.io/_uploads/SJjClxajWe.png) ### 原始型別(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 即可宣告。 ![image](https://hackmd.io/_uploads/BktGNbTibl.png) 從上圖測試結果可看到: - `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 (是否包含某字串) ``` 實際執行畫面: ![image](https://hackmd.io/_uploads/Sy4xH-6s-g.png) #### 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 ``` 實際執行畫面: ![image](https://hackmd.io/_uploads/r1n8Sb6s-l.png) 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 ``` 實際的執行畫面: ![image](https://hackmd.io/_uploads/B1zrXzasWe.png) #### 特性三:可重複宣告 ```javascript= var name = "LukeTseng" var name = "Amy" console.log(name) // "Amy" ``` 實際的執行畫面: ![image](https://hackmd.io/_uploads/r1IuXGpo-l.png) ### 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 ``` 實際執行畫面: ![image](https://hackmd.io/_uploads/SyhA7fpoWx.png) #### 特性二:有 Hoisting 但不初始化(TDZ) `let` 也會被 Hoisting,但不會初始化為 `undefined`,在宣告前使用會直接報錯。 這段宣告前的禁區稱為 `Temporal Dead Zone`(TDZ,暫時性死區): ```javascript= console.log(y) // ReferenceError: Cannot access 'y' before initialization let y = 20 ``` 實際執行畫面: ![image](https://hackmd.io/_uploads/SkHo4z6oWg.png) #### 特性三:不能重複宣告,但可以重新賦值 ```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] ``` 實際執行畫面: ![image](https://hackmd.io/_uploads/Hy4SUfTobe.png) ### `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)