JavaScript
Built-in Classes
String
เรียนรู้ `String` built-in class — static methods, การแปลงค่าด้วย `String()` และ prototype methods สำหรับจัดการข้อความในงานจริง
`String` คือ built-in class — มี static methods และทำ auto-boxing
`String` เป็น built-in class ใน JavaScript — เป็น object ระดับ global ที่มี **static methods** ให้เรียกใช้ได้ทันทีโดยไม่ต้องสร้าง instance ด้วย `new` คุณรู้จัก `String` ในฐานะ primitive type มาแล้ว (บทที่ 12) และใช้ prototype methods ของ string มาหลายบทแล้ว: `.trim()`, `.slice()`, `.replace()`, `.includes()`, `.split()` — ทั้งหมดนี้อยู่บน `String.prototype` `String` เองทำงานเหมือน `Number` (บทที่ 96): - `let name = "Mali";` — ตัวแปร `name` เป็น primitive `string` - `String` — เป็น class/object ที่เก็บ static methods เวลาเราเรียก method บน primitive เช่น `"hello".toUpperCase()` — JavaScript จะทำ **auto-boxing** โดย wrap `"hello"` เป็น `String` object ชั่วคราว เรียก method แล้วคืนค่าเป็น primitive ทันที นอกจากนี้ `String()` ยังใช้เป็น **conversion function** เพื่อแปลงค่าจาก type อื่นเป็น string — `String(42)` → `"42"` — โดยไม่ต้องใช้ `new` (ซึ่งการทำ `new String()` จะสร้าง wrapper object ที่ไม่แนะนำให้ใช้)
// static method — เรียกโดยตรง ไม่ต้องสร้าง instance
console.log(String.fromCharCode(65, 66, 67)); // "ABC"
// String() ใช้เป็น converter
console.log(String(42)); // "42"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
// auto-boxing — primitive เรียก method ได้ชั่วคราว
console.log("hello".toUpperCase()); // "HELLO"
console.log("hello".length); // 5
// อย่าใช้ new String() — สร้าง wrapper object ไม่ใช่ primitive
const wrapped = new String("hello");
console.log(typeof wrapped); // "object"
console.log(typeof "hello"); // "string"- `String` เป็น built-in class — มี static methods ให้เรียกใช้ได้ทันทีผ่าน `String.`
- primitive `string` กับ `String` class เป็นคนละอย่าง — แต่มันทำงานร่วมกันผ่าน auto-boxing
- ใช้ `String()` เป็น function แปลงค่า — อย่าใช้ `new String()`
- ทุก method ที่คุณใช้กับ string อยู่บน `String.prototype` — เรียนแล้วหลายบทและจะเรียนเพิ่มในบทนี้
`String.fromCharCode()` และ `String.fromCodePoint()` — สร้าง string จาก Unicode
`String.fromCharCode(...codes)` สร้าง string จาก numeric code points (UTF-16) — รับ parameter ได้หลายตัว **Use case หลัก**: แปลง keyCode จาก keyboard event เป็นตัวอักษรที่กด — `String.fromCharCode(65)` → `"A"`, `String.fromCharCode(48)` → `"0"` `String.fromCodePoint(...codes)` เหมือนกันแต่รองรับ code points ที่เกิน 0xFFFF — เช่น emoji (`String.fromCodePoint(0x1F600)` → `"😀"`) — `fromCharCode` ใช้กับ emoji ไม่ได้เพราะ emoji ใช้ code point ที่ใหญ่กว่า 16-bit **ข้อแตกต่างสำคัญ**: - `fromCharCode` — รับ UTF-16 code units (สูงสุด 0xFFFF) — ใช้กับตัวอักษรทั่วไป, ASCII, อักขระไทย - `fromCodePoint` — รับ Unicode code points (สูงสุด 0x10FFFF) — ใช้กับ emoji, ตัวอักษรพิเศษที่หายาก
// String.fromCharCode — ตัวอักษรตั้งแต่ ASCII
console.log(String.fromCharCode(65)); // "A"
console.log(String.fromCharCode(97)); // "a"
console.log(String.fromCharCode(48)); // "0"
console.log(String.fromCharCode(32)); // " " ← space
// ส่งหลายตัวได้
console.log(String.fromCharCode(72, 105)); // "Hi"
// อักษรไทย — code point ในช่วง 0E00-0E7F
console.log(String.fromCharCode(0x0E01)); // "ก"
console.log(String.fromCharCode(0x0E2A, 0x0E27, 0x0E31, 0x0E2A, 0x0E14, 0x0E35));
// "สวัสดี"
// String.fromCodePoint — รองรับ code point ใหญ่กว่า 0xFFFF
console.log(String.fromCodePoint(0x1F600)); // "😀"
console.log(String.fromCodePoint(0x1F44D)); // "👍"
console.log(String.fromCodePoint(65)); // "A" ← ใช้กับ ASCII ได้เหมือนกัน| method | parameter | ขอบเขต | ใช้กับอะไร |
|---|---|---|---|
| `String.fromCharCode(n, ...)` | UTF-16 code units | 0 — 65535 (0xFFFF) | ASCII, ไทย, จีน, ตัวอักษรทั่วไป — keyboard event |
| `String.fromCodePoint(n, ...)` | Unicode code points | 0 — 1114111 (0x10FFFF) | emoji, ตัวอักษรหายาก, ทุกอย่างที่ fromCharCode ทำได้ |
- ใช้ `String.fromCharCode` เมื่อรับ keyCode/key จาก event — `event.keyCode` คืนเลข UTF-16 code unit
- โยงกับ `.charCodeAt()` (prototype method) — `"A".charCodeAt(0)` → `65` → `String.fromCharCode(65)` → `"A"` (กลับด้านกัน)
- ใช้ `String.fromCodePoint` เมื่ออาจเจอ emoji หรือตัวอักษรพิเศษ — ครอบคลุมทุก Unicode
- `String.fromCodePoint` เป็น method ใหม่กว่า (ES6) — แนะนำให้ใช้แทน `fromCharCode` ในโค้ดใหม่ เว้นแต่ต้องการ compatibility เก่า
`String.raw()` — template tag ที่ไม่ตีความ escape sequence
`String.raw` เป็น static tag function ใช้กับ template literal — มันคืนค่า string ดิบ โดย **ไม่ตีความ** `\n`, `\t`, `\uXXXX` หรือ escape ใด ๆ ปกติ template literal จะแปลง `\n` เป็นบรรทัดใหม่, `\t` เป็นแท็บ — แต่ `String.raw` เก็บ `\n` เป็นตัวอักษร `\` ตามด้วย `n` จริง ๆ **Use case หลัก**: - เขียน regular expression — ไม่ต้อง double-escape backslash (`String.raw`\`\d+\.\d+\`` แทน `"\\d+\\.\\d+"`) - เขียน Windows file path — `String.raw`\`C:\Users\Mali\`` แทน `"C:\\Users\\Mali"` - เก็บ string ที่ต้องรักษา escape ไว้ (เช่น JSON ที่มี `\n` ใน value) `String.raw` ต้องใช้กับ tagged template — ใส่ `String.raw` นำหน้า backtick: `String.raw`\`...\` — จะใช้กับ single/double quote ไม่ได้
// template literal ปกติ — ตีความ escape
const normal = `บรรทัดที่ 1\nบรรทัดที่ 2`;
console.log(normal);
// บรรทัดที่ 1
// บรรทัดที่ 2
// String.raw — ไม่ตีความ escape เห็น \n เป็นตัวอักษร
const raw = String.raw`บรรทัดที่ 1\nบรรทัดที่ 2`;
console.log(raw);
// "บรรทัดที่ 1\nบรรทัดที่ 2"
// === use case: regex — ไม่ต้อง double-escape ===
// แบบเดิม: ต้องเขียน \\d แทน \d
const oldRegex = new RegExp("\\d+\\.\\d+");
console.log(oldRegex); // /\d+\.\d+/
// String.raw: เขียน \d ได้ตรง ๆ
const rawRegex = new RegExp(String.raw`\d+\.\d+`);
console.log(rawRegex); // /\d+\.\d+/
// === use case: Windows path ===
const winPath = String.raw`C:\Users\Mali\Documents`;
console.log(winPath); // "C:\Users\Mali\Documents"| โค้ด | template literal ปกติ | String.raw |
|---|---|---|
| `` `a\nb` `` | `"a\nb"` (ตีความ `\n` → newline) | `"a\\nb"` (เก็บ `\n` เป็นตัวอักษร) |
| `` `\u0041` `` | `"A"` (ตีความ `\u0041` → `A`) | `"\\u0041"` (เก็บ `\u` เป็นตัวอักษร) |
| `` `C:\path` `` | `"C:path"` (backslash หาย — `\p` ไม่ใช่ escape ที่รู้จัก) | `"C:\\path"` (เก็บ `\` ไว้) |
- `String.raw` ลด double-escaping — regex ที่ซับซ้อนจะอ่านง่ายขึ้นมาก
- ใช้ `String.raw` กับ regex, Windows path, และ string ที่ต้องเก็บ escape ไว้
- ระวัง: `String.raw` ใช้กับ tagged template เท่านั้น — `String.raw\`...\`` — ใช้กับ `'...'` หรือ `"..."` ไม่ได้
- `String.raw` ยังตีความ placeholder (`` \`${expr}\` ``) ตามปกติ — แค่ไม่ตีความ escape
`.charAt()`, `.indexOf()`, `.lastIndexOf()` — เข้าถึงตัวอักษรและหาตำแหน่ง
**`.charAt(index)`** — คืนตัวอักษรที่ตำแหน่ง `index` (เริ่มที่ 0) เหมือนใช้ bracket notation `s[idx]` แต่ `.charAt` คืน `""` (empty string) เมื่อ index เกินขอบเขต แทนที่จะเป็น `undefined` **`.indexOf(search)`** — คืนตำแหน่ง **แรก** ที่เจอ substring ถ้าไม่เจอคืน `-1` **`.lastIndexOf(search)`** — เหมือนกันแต่หาจาก **ท้าย** string ทั้ง `.indexOf` และ `.lastIndexOf` รับ parameter ตัวที่สองเป็น `fromIndex` — ตำแหน่งเริ่มต้นที่จะเริ่มหา **การเปรียบเทียบเป็น case-sensitive** — `"Hello".indexOf("h")` → `-1` เพราะ `h` กับ `H` ถือว่าต่างกัน **Use case**: - `.charAt(0)` → เช็กตัวแรกของ string โดยไม่ต้องกลัว `undefined` - `.indexOf("@")` → หาตำแหน่ง `@` ใน email เพื่อแยก username กับ domain - `.lastIndexOf(".")` → หานามสกุลไฟล์ (ตำแหน่งของ `.` ตัวสุดท้าย)
const str = "Hello World";
// .charAt — ดึงตัวอักษรที่ตำแหน่ง
console.log(str.charAt(0)); // "H"
console.log(str.charAt(6)); // "W"
console.log(str.charAt(20)); // "" ← เกินขอบเขต ไม่ใช่ undefined
console.log(str[20]); // undefined ← bracket notation ต่างกันตรงนี้
// .indexOf — หาตำแหน่งแรกที่เจอ
console.log(str.indexOf("o")); // 4
console.log(str.indexOf("World")); // 6
console.log(str.indexOf("o", 5)); // 7 ← เริ่มหาจากตำแหน่ง 5
console.log(str.indexOf("zoo")); // -1 ← ไม่เจอ
// .lastIndexOf — หาจากท้าย
console.log(str.lastIndexOf("o")); // 7 ← "o" ใน "World"
console.log(str.lastIndexOf("l")); // 9 ← "l" ตัวสุดท้าย
// === use case: แยก username จาก email ===
const email = "mali@example.com";
const atPos = email.indexOf("@");
const username = email.slice(0, atPos);
const domain = email.slice(atPos + 1);
console.log(username); // "mali"
console.log(domain); // "example.com"
// === use case: หานามสกุลไฟล์ ===
const filename = "document.final.backup.pdf";
const lastDot = filename.lastIndexOf(".");
const ext = filename.slice(lastDot + 1);
console.log(ext); // "pdf"| method | คืนค่า | เมื่อไม่เจอ | ใช้เมื่อไหร่ |
|---|---|---|---|
| `.charAt(index)` | ตัวอักษรที่ตำแหน่ง (string) | `""` (empty string) | เข้าถึงอักขระเดี่ยว — ต้องการผลลัพธ์ที่ไม่เป็น `undefined` |
| `.indexOf(s, from)` | ตำแหน่งแรกที่เจอ (number) | `-1` | เช็กว่ามี substring หรือไม่, หาตำแหน่งตัวคั่น (@, :, /) |
| `.lastIndexOf(s, from)` | ตำแหน่งสุดท้ายที่เจอ (number) | `-1` | หานามสกุลไฟล์, path segment สุดท้าย, URL fragment |
- `.indexOf` และ `.lastIndexOf` เป็น case-sensitive — `"MA".indexOf("ma")` → `-1`
- ใช้ `.indexOf(s) !== -1` เช็กว่ามี substring หรือไม่ — แต่ `.includes()` (บทที่ 82) สื่อความหมายชัดกว่า
- `.indexOf` ใช้เมื่อต้องการ **ตำแหน่ง** — `.includes()` ใช้เมื่อต้องการแค่รู้ว่า **มีหรือไม่มี**
- `.charAt(index)` คืน `""` แทน `undefined` เมื่อ index เกินขอบเขต — ปลอดภัยกว่า bracket notation ในบางกรณี
`.padStart()`, `.padEnd()`, `.repeat()` — จัด format และสร้างข้อความซ้ำ
**`.padStart(targetLength, padString)`** — เติม `padString` ทางด้านซ้ายจน string มีความยาวเท่ากับ `targetLength` — ถ้า string ยาวพอแล้วจะคืนค่าเดิม **`.padEnd(targetLength, padString)`** — เหมือนกันแต่เติมทางขวา **`.repeat(count)`** — สร้าง string ที่เกิดจากการนำ string ต้นฉบับมาต่อกัน `count` ครั้ง **Use case**: - `.padStart(5, "0")` — เติมเลข 0 ด้านหน้าเลขใบสั่งซื้อ (`"42"` → `"00042"`) - `.padEnd(20, " ")` — จัดคอลัมน์ console table ให้กว้างเท่ากัน - `.repeat(3)` — สร้าง separator (`"=".repeat(40)`), star rating display (`"★".repeat(4) + "☆".repeat(1)`) `padString` default เป็น `" "` (space) ถ้าไม่ระบุ — `.padStart(10)` จะเติม space 10 ตัว ถ้า `padString` ยาวเกิน `targetLength` จะถูกตัดให้พอดี — `.padStart(2, "abc")` → `"ab"`
// === .padStart — เติมด้านซ้าย ===
console.log("42".padStart(5, "0")); // "00042"
console.log("7".padStart(3, "0")); // "007"
console.log("abc".padStart(10)); // " abc" ← default padString = " "
// ถ้า string ยาวพอแล้ว → ไม่เติม
console.log("hello".padStart(3, "x")); // "hello"
// padString ยาวเกิน → ถูกตัด
console.log("x".padStart(4, "abc")); // "abcx"
// === .padEnd — เติมด้านขวา ===
console.log("42".padEnd(5, ".")); // "42..."
console.log("Chapter 1".padEnd(20, " ")); // "Chapter 1 "
// จัดคอลัมน์ console table
const items = [
{ name: "Apple", price: 35 },
{ name: "Watermelon", price: 120 },
];
for (const item of items) {
const label = item.name.padEnd(12, " ");
const val = String(item.price).padStart(4, " ");
console.log(`${label} ${val} THB`);
}
// Apple 35 THB
// Watermelon 120 THB
// === .repeat — สร้างข้อความซ้ำ ===
console.log("la".repeat(3)); // "lalala"
console.log("=".repeat(40)); // "========================================"
console.log("☆".repeat(0)); // ""
console.log("★".repeat(4) + "☆".repeat(1)); // "★★★★☆" ← star rating| method | parameter | คืนค่า | ใช้เมื่อไหร่ |
|---|---|---|---|
| `.padStart(len, s)` | ความยาวเป้าหมาย + string ที่เติม | string ที่ยาว ≥ len | format ตัวเลขมีเลข 0 นำหน้า, จัด alignment ขวา |
| `.padEnd(len, s)` | ความยาวเป้าหมาย + string ที่เติม | string ที่ยาว ≥ len | จัดคอลัมน์ให้กว้างเท่ากัน, เพิ่ม placeholder ต่อท้าย |
| `.repeat(n)` | จำนวนครั้งที่ทำซ้ำ | string เดิมซ้ำ n ครั้ง | สร้าง separator, repeating pattern, star rating |
- `padString` มีค่า default เป็น `" "` (space) — `.padStart(10)` ก็ใช้ได้
- ถ้า `padString` ยาวเกินกว่าที่เหลือ — JavaScript จะตัด `padString` ไม่ใช่ตัด string ต้นฉบับ
- `.repeat(0)` คืน `""` — `.repeat(จำนวนลบ)` throw `RangeError`
- `.repeat` ใช้กับ string เท่านั้น — ใช้กับ number ต้องแปลงก่อน: `String(n).repeat(3)`
`.toLowerCase()`, `.toUpperCase()`, `.localeCompare()` — แปลง case และเปรียบเทียบ
**`.toLowerCase()`** — แปลง string เป็นตัวพิมพ์เล็กทั้งหมด (lowercase) **`.toUpperCase()`** — แปลง string เป็นตัวพิมพ์ใหญ่ทั้งหมด (uppercase) ทั้งสอง method นี้ไม่เปลี่ยน string ต้นฉบับ — string เป็น immutable — จะคืน string ใหม่เสมอ **Use case หลัก**: normalize case ก่อนเปรียบเทียบหรือค้นหา — `input.toLowerCase().includes(keyword.toLowerCase())` ทำให้ค้นหา "JavaScript" เจอทั้ง "JAVASCRIPT" และ "javascript" **`.localeCompare(compareString, locales)`** — เปรียบเทียบ string ตาม locale (ภาษา/ภูมิภาค) — คืนค่าลบถ้ามาก่อน, 0 ถ้าเท่ากัน, ค่าบวกถ้ามาหลัง — ใช้สำหรับการเรียงลำดับภาษาไทยที่ถูกต้อง **`"ก".localeCompare("ข", "th")` → `-1` (ก มาก่อน ข)** — ถูกต้องตามพจนานุกรมไทย ถ้าใช้ `<` หรือ `>` เปรียบเทียบ string — จะเปรียบเทียบตาม Unicode code point ซึ่งอาจเรียงภาษาไทยผิด — ควรใช้ `.localeCompare` ในการ sort
// === .toLowerCase / .toUpperCase ===
const text = "Hello JavaScript";
console.log(text.toLowerCase()); // "hello javascript"
console.log(text.toUpperCase()); // "HELLO JAVASCRIPT"
// string ต้นฉบับไม่เปลี่ยน — immutable
console.log(text); // "Hello JavaScript"
// normalize case ก่อนค้นหา
const keyword = "java";
console.log(text.toLowerCase().includes(keyword.toLowerCase())); // true
// === .localeCompare — เปรียบเทียบตาม locale ===
// เปรียบเทียบภาษาไทย
console.log("ก".localeCompare("ข", "th")); // -1 ← ก มาก่อน ข ✅
console.log("ข".localeCompare("ก", "th")); // 1 ← ข มาหลัง ก ✅
console.log("ก".localeCompare("ก", "th")); // 0 ← เท่ากัน
// เทียบกับ < > — เรียงตาม code point ซึ่งอาจผิด
console.log("ก" < "ข"); // true ← บังเอิญถูก
// แต่กับอักษรพิเศษ การเรียงด้วย < > อาจผิด — localeCompare ปลอดภัยกว่า
// sort array ตามพจนานุกรมไทย
const thaiWords = ["ส้ม", "มะม่วง", "กล้วย", "เงาะ"];
thaiWords.sort((a, b) => a.localeCompare(b, "th"));
console.log(thaiWords);
// ["กล้วย", "เงาะ", "มะม่วง", "ส้ม"] ← เรียงถูกตามพจนานุกรม
// .localeCompare มี option ให้ตั้งค่า sensitivity
console.log("a".localeCompare("A", undefined, { sensitivity: "base" }));
// 0 ← ถือว่า a = A เมื่อตั้ง sensitivity: "base"| method | คืนค่า | ใช้เมื่อไหร่ |
|---|---|---|
| `.toLowerCase()` | string ตัวพิมพ์เล็กทั้งหมด | normalize case ก่อนเปรียบเทียบ, แปลง input จาก form เป็น lowercase |
| `.toUpperCase()` | string ตัวพิมพ์ใหญ่ทั้งหมด | normalize case ก่อนเปรียบเทียบ, แปลงรหัสที่ต้องเป็น uppercase |
| `.localeCompare(s, locale)` | จำนวนลบ / 0 / จำนวนบวก | เรียงลำดับภาษาไทย, multi-language sorting, case-insensitive compare |
- string ทุกตัวเป็น immutable — `.toLowerCase()` และ method ทุกตัวคืน string ใหม่ ไม่เปลี่ยนต้นฉบับ
- ใช้ `.toLowerCase()` หรือ `.toUpperCase()` คู่กับ `.includes()` / `.startsWith()` เพื่อทำ case-insensitive search
- `.localeCompare` เรียงภาษาไทยถูกต้อง — ใช้กับ `.sort()` แทน `<` หรือ `>`
- `.localeCompare` ใช้ `"th"` เป็น locale สำหรับภาษาไทย — parameter ตัวที่สองเป็น string