โหมดมืด
บทที่ 08 — String, Regular Expression และวันเวลา
💡 เคยเห็น regex มาก่อนหรือยัง? ใน บทที่ 07 Lab 3 เราใช้
/\s+/กับsplitแบบไม่ได้อธิบาย — บทนี้คือคำอธิบายเต็มของ regex ตัวนั้น
บทนี้รวม 3 เรื่องที่เจอในงานจริงตลอด — จัดการข้อความ, ค้นหา/ตรวจสอบ pattern ด้วย regex, และทำงานกับวันเวลา
อ่านจบบทนี้คุณจะ:
- จัดการ string ได้คล่อง
- เข้าใจ Regular Expression — เครื่องมือค้นหา pattern ที่ทรงพลัง
- ทำงานกับวันเวลาได้ พร้อมรู้กับดักของ
Date - แสดงผลตัวเลข/วันที่ตาม locale (ภาษาไทย พ.ศ.)
ใช้เวลา 2-3 ชั่วโมง
Part 1: String Methods
ทบทวนจากบทที่ 02 — เราเรียน string มาบ้างแล้ว บทนี้ลงลึกขึ้น
1.1 Methods ที่ใช้บ่อย
string มี method สำเร็จรูปให้จัดการข้อความมากมาย — ตัดช่องว่าง (trim), เปลี่ยนตัวพิมพ์ (toUpperCase), ค้นหา (includes, indexOf), แทนที่ (replace, replaceAll) รวมตัวที่ใช้บ่อยไว้ด้านล่าง:
javascript
const s = " Hello, World! ";
s.length // 17 — รวมช่องว่าง
s.trim() // "Hello, World!" — ตัดช่องว่างหน้า-หลัง
s.toUpperCase() // " HELLO, WORLD! "
s.toLowerCase()
s.includes("World") // true
s.startsWith(" Hello") // true
s.endsWith("! ") // true
s.indexOf("World") // 9 — ตำแหน่งที่เจอ (ไม่เจอคืน -1)
s.replace("World", "JS") // แทนที่ครั้งแรก
s.replaceAll("l", "L") // แทนที่ทุกตัว
s.repeat(3) // ทำซ้ำ 3 รอบ1.2 ตัด string — slice
slice ตัดเอาบางส่วนของ string ตามตำแหน่ง index (รองรับเลขติดลบเพื่อนับจากท้าย) — คืน string ใหม่ ไม่แก้ของเดิม:
javascript
const s = "JavaScript";
s.slice(0, 4) // "Java" — index 0 ถึง 4 (ไม่รวม 4)
s.slice(4) // "Script" — จาก index 4 ถึงท้าย
s.slice(-6) // "Script" — 6 ตัวท้าย1.3 แยก string — split
split แยก string ออกเป็น array ตามตัวคั่นที่กำหนด (เช่น comma, ช่องว่าง, หรือ "" เพื่อแยกทีละตัวอักษร) — ตรงข้ามกับ join ของ array:
javascript
"a,b,c".split(",") // ["a", "b", "c"]
"hello".split("") // ["h", "e", "l", "l", "o"]
"one two three".split(" ") // ["one", "two", "three"]1.4 เติมให้ครบความยาว — padStart / padEnd
javascript
"5".padStart(3, "0") // "005" — เติม 0 ข้างหน้าให้ครบ 3 ตัว
"5".padEnd(3, "-") // "5--"ใช้บ่อยตอนจัดรูปแบบ — เช่นแสดงเวลา 09:05:
javascript
const hour = 9, minute = 5;
`${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`; // "09:05"1.5 ⚠️ String แก้ไม่ได้
ทบทวน — string เปลี่ยนแปลงไม่ได้ (immutable) ทุก method สร้าง string ใหม่ ไม่แก้ของเดิม:
javascript
let s = "hello";
s.toUpperCase(); // คืน "HELLO" แต่ s ยังเป็น "hello"
s = s.toUpperCase(); // ต้อง assign กลับ1.6 Template Literal — ทบทวน
จากบทที่ 02 — `...` แทรกตัวแปรด้วย ${ } และเขียนหลายบรรทัดได้
ความสามารถเพิ่มเติม — tagged template (เทคนิคที่ใส่ชื่อฟังก์ชันหน้า backtick เพื่อให้ฟังก์ชันนั้นประมวลผล template literal แทนเรา):
🚀 โซนขั้นสูง — ข้ามได้: tagged template เป็นเทคนิคที่มือใหม่แทบไม่ได้ใช้เอง ข้ามได้เลย แค่รู้ว่ามีไว้ตอนเจอใน library โค้ดด้านล่างใช้
.reduce()ที่เรียนในบทที่ 07 — ถ้ายังไม่แม่นไม่ต้องพยายามอ่านตอนนี้
javascript
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `**${values[i]}**` : "");
}, "");
}
const name = "Alice";
highlight`สวัสดี ${name} ยินดีต้อนรับ`;
// "สวัสดี **Alice** ยินดีต้อนรับ"ใช้น้อยสำหรับมือใหม่ — แต่ library บางตัว (เช่น styled-components) ใช้เทคนิคนี้
Part 2: Unicode — เรื่องที่ต้องรู้
2.1 ปัญหาความยาว emoji
javascript
"hello".length // 5 — ปกติ
"😀".length // 2 — ⚠️ อ้าว ทำไม 2?JavaScript เก็บ string เป็น "หน่วยเล็ก ๆ" ที่เรียกว่า code unit ขนาด 16 บิต (bit คือหน่วยข้อมูลที่เล็กที่สุดในคอมพิวเตอร์ — ไม่ต้องเข้าใจลึกก็ได้) ตัวอักษรปกติใช้ 1 code unit แต่ emoji (และอักขระพิเศษบางตัว) ใช้ 2 code unit — เพราะแบบนี้ .length ของ "😀" ถึงนับเป็น 2
วิธีนับให้ถูก — ใช้ spread หรือ for...of:
javascript
[..."😀"].length // 1 — ถูกต้อง
[..."hello😀"].length // 6 — ถูกต้อง⚠️ ยังไม่จบ — emoji รวม (ZWJ) และธงประเทศ: วิธี spread ใช้ได้กับ emoji เดี่ยว ๆ — แต่ emoji ที่เป็น "การรวมกัน" เช่น 👨👩👧 (ครอบครัว — รวมจาก 3 ตัวด้วยตัวเชื่อม ZWJ) หรือ 🇹🇭 (ธงไทย — รวมจาก 2 ตัว regional indicator) ยังถูกนับเกิน 1:
javascript
[..."👨👩👧"].length // 5 — ❌ จริง ๆ ควรเป็น 1
[..."🇹🇭"].length // 2 — ❌ จริง ๆ ควรเป็น 1ถ้าต้องการนับ "ตัวอักษรที่ผู้ใช้เห็น" (grapheme cluster) จริง ๆ ใช้ Intl.Segmenter (ES2022):
javascript
const seg = new Intl.Segmenter("th", { granularity: "grapheme" });
[...seg.segment("👨👩👧")].length // 1 — ถูกต้อง
[...seg.segment("🇹🇭")].length // 1 — ถูกต้อง→ ส่วนใหญ่งานทั่วไปไม่ต้องกังวล — แต่ถ้าทำงานกับ emoji/ภาษาที่ซับซ้อน ระวังเรื่องนี้
Part 3: Regular Expression — เครื่องมือค้นหา pattern
Regular Expression (Regex) = ชุดสัญลักษณ์สำหรับอธิบาย "รูปแบบ (pattern)" ของข้อความ — ใช้ค้นหา ตรวจสอบ แทนที่
ตัวอย่างที่ regex แก้ได้:
- "ข้อความนี้เป็น email ที่ถูกต้องไหม"
- "ดึงตัวเลขทั้งหมดออกจากข้อความ"
- "แทนที่ช่องว่างซ้ำ ๆ ด้วยช่องว่างเดียว"
3.1 สร้าง Regex
สร้าง regex (รูปแบบสำหรับค้นหา/ตรวจข้อความ) ได้ 2 วิธี — เขียนตรง ๆ ระหว่าง / / (นิยมสุด) หรือสร้างจาก string ด้วย new RegExp() (ใช้เมื่อ pattern มาจากตัวแปร):
javascript
const re1 = /abc/; // เขียนตรง ๆ ระหว่าง / /
const re2 = new RegExp("abc"); // สร้างจาก string3.2 Pattern พื้นฐาน — มาทำความรู้จักทีละตัว
ตัวอักษรพิเศษสำหรับ "ชนิดของตัวอักษร":
text
\d = ตัวเลข 0-9
\w = ตัวอักษร a-z, A-Z, 0-9, _ (word character)
\s = ช่องว่าง (space, tab, ขึ้นบรรทัด)
. = ตัวอักษรอะไรก็ได้ (ยกเว้นขึ้นบรรทัด)
\D = ไม่ใช่ตัวเลข
\W = ไม่ใช่ word character
\S = ไม่ใช่ช่องว่างกำหนดกลุ่มตัวอักษรเอง:
text
[abc] = a หรือ b หรือ c
[a-z] = ตัวเล็ก a ถึง z
[0-9] = ตัวเลข 0 ถึง 9 (เหมือน \d)
[^abc] = อะไรก็ได้ที่ไม่ใช่ a, b, cจำนวนครั้ง (quantifier):
text
* = 0 ครั้งขึ้นไป
+ = 1 ครั้งขึ้นไป
? = 0 หรือ 1 ครั้ง
{3} = 3 ครั้งพอดี
{3,} = 3 ครั้งขึ้นไป
{3,5} = 3 ถึง 5 ครั้งตำแหน่ง:
text
^ = ต้นข้อความ
$ = ท้ายข้อความอื่น ๆ:
text
| = หรือ (เช่น cat|dog = "cat" หรือ "dog")
( ) = จับกลุ่ม3.3 อ่าน pattern เป็นตัวอย่าง
วิธีเก่ง regex คือฝึก "อ่าน" ทีละส่วน — ลองดูตัวอย่าง pattern จริงด้านล่างแล้วแปลว่ามันหาอะไร โดยอาศัยตารางสัญลักษณ์จากหัวข้อก่อน:
มาฝึกอ่าน regex:
javascript
/\d+/ // ตัวเลข 1 ตัวขึ้นไป
/^\d{4}$/ // ตัวเลข 4 ตัวพอดี ตั้งแต่ต้นถึงท้าย (เช่น "2026")
/[a-z]+/ // ตัวอักษรเล็ก 1 ตัวขึ้นไป
/\s+/ // ช่องว่าง 1 ตัวขึ้นไป
/^0\d{8,9}$/ // เริ่มด้วย 0 ตามด้วยตัวเลข 8-9 ตัว (เบอร์โทรไทย)3.4 Flags — ตัวเลือกท้าย regex
Flag คือ "ตัวเลือก" ที่เปิด/ปิดพฤติกรรมการค้นหา — ใส่ตัวอักษรหลัง / ปิดท้าย regex: g (หาทุกจุด ไม่ใช่แค่จุดแรก), i (ไม่สนตัวเล็ก-ใหญ่), m (ทำงานข้ามบรรทัด) ใส่หลายตัวพร้อมกันได้:
ใส่ตัวอักษรหลัง / ปิดท้าย:
javascript
/abc/g // g = global — หาทุกจุด (ไม่ใช่แค่จุดแรก)
/abc/i // i = ignore case — ไม่สนตัวเล็ก-ใหญ่
/abc/m // m = multiline — ^ $ ทำงานต่อบรรทัด
/abc/gi // ใส่หลายตัวได้3.5 ใช้ Regex — test, match, replace
test — เช็คว่า match ไหม (คืน true/false):
javascript
/\d+/.test("abc123") // true — มีตัวเลขไหม
/^\d+$/.test("abc123") // false — เป็นตัวเลขล้วนไหม → ไม่
/^\d+$/.test("12345") // truematch — ดึงสิ่งที่ match ออกมา:
javascript
"abc 123 def 456".match(/\d+/g) // ["123", "456"] — ดึงตัวเลขทุกชุดreplace — แทนที่:
javascript
"hello world".replace(/\s+/g, " ") // "hello world" — ยุบช่องว่างซ้ำ
"abc123".replace(/\d/g, "*") // "abc***" — แทนตัวเลขด้วย *split ด้วย regex:
javascript
"a1b2c3".split(/\d/) // ["a", "b", "c", ""] — แยกที่ตัวเลข
// หมายเหตุ: "" ท้ายสุดเกิดเพราะ string จบด้วย "3" (ตัวเลข) → split แล้วได้ string ว่างหลังตัวสุดท้าย3.6 Capturing Group — ดึงส่วนย่อย
( ) จับกลุ่ม — ดึงส่วนย่อยของ match ออกมาได้:
javascript
const date = "2026-05-20";
const match = date.match(/(\d{4})-(\d{2})-(\d{2})/);
// match[0] = "2026-05-20" — ทั้งหมด
// match[1] = "2026" — กลุ่มแรก
// match[2] = "05" — กลุ่มสอง
// match[3] = "20" — กลุ่มสาม⚠️ ระวัง: ถ้า string ไม่ match pattern เลย —
match()คืนnullไม่ใช่ array ว่าง → ถ้าเข้าถึงnull[0]จะ error วิธีป้องกัน — ใช้ optional chaining?.:javascriptconst date = someString.match(/(\d{4})-(\d{2})-(\d{2})/)?.[0]; // undefined ถ้าไม่เจอ
Named group — ตั้งชื่อกลุ่ม อ่านง่ายขึ้น:
javascript
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { groups } = "2026-05-20".match(re);
console.log(groups.year); // "2026"
console.log(groups.month); // "05"3.7 Pattern ที่ใช้บ่อยในงานจริง
รวม regex สำเร็จรูปที่ใช้บ่อยในงานจริง — ตรวจ email, เบอร์โทรไทย, ยุบช่องว่าง, ดึงตัวเลข ก๊อปไปปรับใช้ได้เลย:
javascript
// เช็ค email (แบบง่าย — สำหรับ feedback ทันทีฝั่ง frontend เท่านั้น)
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
// เบอร์โทรไทย
/^0\d{8,9}$/
// ยุบช่องว่างหลายตัวเป็นช่องเดียว
text.replace(/\s+/g, " ")
// ตัดตัวอักษรที่ไม่ใช่ตัวเลขออก
"abc123def456".replace(/\D/g, "") // "123456"
// ดึงตัวเลขทั้งหมด
"ราคา 250 บาท ลด 50".match(/\d+/g) // ["250", "50"]⚠️ อย่าใช้ regex ตรวจ email ใน production:
- ไม่ครอบคลุมจริง — มาตรฐาน email (RFC 5322) ซับซ้อนมาก regex สั้น ๆ ปฏิเสธ email ถูก ๆ หลายแบบ และผ่าน email ผิด ๆ หลายแบบ
- ReDoS — regex email ที่ "ดูครอบคลุม" หลายแบบใน internet (มักมี quantifier ซ้อนกัน เช่น
(a+)+) มีปัญหา catastrophic backtracking — ผู้ใช้ส่ง input ที่จงใจมาให้ → server ค้างหลายวินาที (เป็น Denial of Service) — pattern ง่าย ๆ อย่าง/^[^\s@]+@[^\s@]+\.[^\s@]+$/ที่ใช้ด้านบนไม่มี quantifier ซ้อนแบบนั้น จึงไม่เสี่ยง ReDoS แต่ยังไม่ครอบคลุม email ที่ถูกต้องตามมาตรฐานอยู่ดี- ถ้าจะใช้ฝั่ง frontend ให้ตรวจความยาวก่อนเสมอ (ป้องกัน input ที่ยาวผิดปกติ):
if (email.length > 254) return false- ทางที่ดีกว่า:
- HTML5:
<input type="email">ให้ browser ตรวจให้- Library ที่ผ่านการทดสอบมาแล้ว เช่น
validator.js(isEmail)- ฝั่ง backend: ส่ง email ยืนยันจริง ๆ — เป็นวิธีเดียวที่พิสูจน์ว่า email ใช้งานได้
3.8 ⚠️ ข้อควรระวังของ Regex
- อย่าใช้ regex แยก HTML/JSON — มันซับซ้อนเกินกว่าที่ regex จัดการได้ ใช้เครื่องมือเฉพาะ
- regex ที่ซับซ้อนอ่านยาก — ใส่ comment อธิบาย หรือแยกเป็นขั้นตอน
- เวลาสงสัย — ลองที่ regex101.com (เว็บทดสอบ regex ที่อธิบายแต่ละส่วนให้)
Part 4: Date — วันเวลา
Date คือ object สำหรับจัดการวันเวลา — และมันมี "กับดัก" เยอะ ต้องรู้
4.1 สร้าง Date
สร้าง Date ได้หลายวิธี — เวลาปัจจุบัน (new Date()), จาก string ISO, จากปี-เดือน-วัน, หรือจาก timestamp ส่วน Date.now() คืนเวลาปัจจุบันเป็นตัวเลข timestamp
💡 timestamp = จำนวนมิลลิวินาที (1/1000 วินาที) ที่นับตั้งแต่วันที่ 1 มกราคม 1970 UTC (เป็นมาตรฐานสากล) — ใช้เป็นตัวแทนของเวลาที่คอมพิวเตอร์จัดการได้ง่าย เช่น
1716163200000คือตัวอย่างเลข timestamp หนึ่งค่า (ตัวเลขนี้เป็นแค่ตัวอย่างประกอบ ไม่ผูกกับปีปัจจุบันของหนังสือเล่มนี้)
javascript
new Date() // ตอนนี้
new Date("2026-05-20") // จาก ISO string (⚠️ ดูกับดักด้านล่าง)
new Date("2026-05-20T10:30:00") // มีเวลาด้วย (เวลาท้องถิ่น)
new Date(2026, 4, 20) // ปี 2026, เดือน 4, วันที่ 20 (เวลาท้องถิ่น)
new Date(1716163200000) // จาก timestamp (มิลลิวินาที)
Date.now() // timestamp ปัจจุบัน (เลขมิลลิวินาที)⚠️ กับดัก ISO string: กฎนี้ตามมาตรฐาน ES2015+ (ไม่ใช่ quirk ของ engine):
"2026-05-20"(วันที่อย่างเดียว — ไม่มีเวลา) → อ่านเป็น UTC เที่ยงคืน"2026-05-20T10:30:00"(มีเวลาแต่ไม่ระบุ TZ) → อ่านเป็น เวลาท้องถิ่นสองแบบนี้ต่างกันคนละเรื่อง! ที่ไทย (+07):
new Date("2026-05-20")จะเป็น 07:00 น. วันที่ 20 พ.ค. ตามเวลาท้องถิ่น — ดูแสดงผลก็เหมือนถูก แต่ถ้าทำ "วันที่ลบกัน" หรือเช็คgetDate()อาจเลื่อนไปวันก่อนหน้าได้ (โดยเฉพาะใน timezone ตะวันตกของ UTC)ทางที่ปลอดภัยกว่า — ใช้รูปแบบที่ "บอก TZ ชัด" หรือใช้ตัวเลข:
javascriptnew Date(2026, 4, 20); // เวลาท้องถิ่น เที่ยงคืน 20 พ.ค. new Date("2026-05-20T00:00:00+07:00"); // ระบุ TZ ไทยชัด ๆ new Date("2026-05-20T00:00:00Z"); // ระบุ UTC ชัด ๆ
4.2 ⚠️ กับดักที่ 1 — เดือนเริ่มนับจาก 0
javascript
new Date(2026, 4, 20)4 ในที่นี้ ไม่ใช่เดือนเมษายน — แต่เป็นเดือนพฤษภาคม!
เพราะ Date นับเดือนจาก 0:
text
0 = มกราคม
1 = กุมภาพันธ์
...
4 = พฤษภาคม
11 = ธันวาคมนี่คือกับดักที่ทำให้มือใหม่ (และมือเก่า) พลาดบ่อยมาก — จำให้ขึ้นใจ: เดือนใน Date เริ่มจาก 0
4.3 อ่านค่าจาก Date
ดึงส่วนต่าง ๆ ของวันเวลาออกมาด้วย method get... — ปี เดือน วัน ชั่วโมง ฯลฯ (ย้ำกับดัก: getMonth() คืนเดือนที่เริ่มนับจาก 0):
javascript
const d = new Date("2026-05-20T14:30:45");
// ⚠️ string นี้ไม่ระบุ TZ → ถูกอ่านเป็นเวลาท้องถิ่น
// getHours() คืนชั่วโมงตาม timezone ของเครื่องที่รัน
// ถ้ารันในไทย (UTC+7) จะได้ 14 — แต่ถ้ารันใน UTC+0 จะได้ 14 เช่นกัน (เพราะ string นี้ local)
d.getFullYear() // 2026
d.getMonth() // 4 — ⚠️ พฤษภาคม (เริ่มจาก 0)
d.getDate() // 20 — วันที่
d.getDay() // วันในสัปดาห์ (0=อาทิตย์, 1=จันทร์, ...)
d.getHours() // 14 — ชั่วโมงตามเวลาท้องถิ่น (ค่านี้เปลี่ยนตาม timezone)
d.getMinutes() // 30
d.getSeconds() // 45
d.getTime() // timestamp (มิลลิวินาที)4.4 ⚠️ กับดักที่ 2 — Date แก้ของเดิม
javascript
const a = new Date("2026-01-01");
const b = a; // b ชี้ Date ตัวเดียวกับ a (จำเรื่อง reference)
b.setFullYear(2030); // แก้ผ่าน b
console.log(a.getFullYear()); // 2030 — ⚠️ a เปลี่ยนด้วย!Date เป็น object → copy แบบ reference → ระวังการแก้
ถ้าอยากได้ Date ใหม่ที่ไม่กระทบเดิม:
javascript
const copy = new Date(original);4.5 แสดงผลวันที่ — ใช้ Intl ดีกว่าทำเอง
อย่าจัดรูปแบบวันที่เอง (d.getDate() + "/" + d.getMonth()) — มันยุ่งและไม่รองรับหลายภาษา
ใช้ Intl.DateTimeFormat — มันจัดรูปแบบตาม "ภาษา/ประเทศ" (เรียกเป็นทางการว่า locale) ให้:
javascript
// ใช้ตัวเลขแทน ISO string เพื่อหลีกเลี่ยงกับดัก UTC vs local (ดู 4.1)
const d = new Date(2026, 4, 20); // เวลาท้องถิ่น เที่ยงคืน 20 พ.ค. 2026
// ภาษาไทย — ได้ พ.ศ. ด้วย!
new Intl.DateTimeFormat("th-TH", {
year: "numeric",
month: "long",
day: "numeric",
timeZone: "Asia/Bangkok", // ⚠️ ระบุ timezone ชัด ๆ เพื่อผลลัพธ์คงที่ทุก environment
}).format(d);
// "20 พฤษภาคม 2569"
// ภาษาอังกฤษ
new Intl.DateTimeFormat("en-US", { timeZone: "Asia/Bangkok" }).format(d);
// "5/20/2026"
// ญี่ปุ่น
new Intl.DateTimeFormat("ja-JP", { timeZone: "Asia/Bangkok" }).format(d);
// "2026/5/20"💡 ถ้าไม่ระบุ
timeZone— ผลลัพธ์จะเปลี่ยนตาม timezone ของ runtime (เครื่องผู้ใช้หรือ server) ซึ่งอาจทำให้แสดงวันที่ผิดได้ในบาง timezone
Intl ฉลาด — ภาษาไทยมันแปลงเป็น พ.ศ. ให้อัตโนมัติ (2569 = 2026 + 543)
4.6 คำนวณวันเวลา
คำนวณวันเวลาทำได้ด้วย set... (บวก/ลบวัน) และการลบ Date สองตัวได้ผลเป็นมิลลิวินาที (หารเพื่อแปลงเป็นวัน) — แต่งานจริงที่ซับซ้อนแนะนำใช้ library (ดูหัวข้อ 4.7):
javascript
const now = new Date();
// บวก 7 วัน
const nextWeek = new Date(now);
nextWeek.setDate(now.getDate() + 7);
// หาความต่างเป็นวัน
const start = new Date("2026-01-01");
const end = new Date("2026-12-31");
const diffMs = end - start; // ความต่างเป็นมิลลิวินาที
const diffDays = diffMs / (1000 * 60 * 60 * 24); // แปลงเป็นวัน
console.log(diffDays); // 364💡 ทำไม 364 ไม่ใช่ 365? เพราะ 1 ม.ค. ถึง 31 ธ.ค. มี "ช่วงห่าง" = 364 วัน (ไม่นับ 1 ม.ค. เองเป็น 1 วันของความต่าง) — ปี 2026 มี 365 วันในปฏิทิน แต่ความต่างระหว่างวันแรกกับวันสุดท้ายของปีคือ 364 วัน เหมือนเดิน 365 ก้าว แต่ระยะห่างระหว่างก้าวแรกกับก้าวสุดท้ายคือ 364 ช่วง
ในบางประเทศที่มี DST (เปลี่ยนเวลาฤดูร้อน/หนาว) ค่านี้อาจกลายเป็นเลขทศนิยม
.999...หรือ.041...เพราะวันนั้นมี 23 หรือ 25 ชั่วโมง — ไทยไม่มี DST ก็เลยไม่เจอกับดักนี้ แต่ถ้าทำงานข้ามประเทศต้องระวังถ้าทำงานกับวันเวลาข้ามประเทศบ่อย ๆ ใช้ library อย่าง date-fns ช่วยคำนวณจะปลอดภัยกว่า
4.7 ในงานจริง — ใช้ library
Date ของ JavaScript จัดการยาก — งานจริงมักใช้ library:
- date-fns — ชุดฟังก์ชันจัดการวันเวลา (แนะนำ)
- Day.js — เล็ก เบา
💡
importคืออะไร? — คำสั่งดึง library มาใช้ จะเรียนละเอียดในบทที่ 10 ตอนนี้แค่รู้ว่า date-fns ต้องติดตั้งก่อน (npm install date-fns) แล้วนำเข้าแบบนี้
javascript
import { format, addDays, differenceInDays } from "date-fns";
format(new Date("2026-05-20"), "yyyy-MM-dd"); // "2026-05-20" — ใช้วันที่แน่นอนให้ผลคงที่
addDays(new Date(), 7); // บวก 7 วัน
differenceInDays(end, start); // หาความต่าง→ มี Temporal ที่เป็น API ใหม่ของ JS สำหรับวันเวลาโดยเฉพาะ — ออกแบบมาแก้กับดักของ Date ทั้งหมด (เดือนเริ่มจาก 1, immutable, แยก "วันที่อย่างเดียว" กับ "วันที่+เวลา" ชัดเจน, จัดการ timezone ตรง ๆ)
สถานะ Temporal ใน TC39 และการรองรับใน engine ต่าง ๆ (Node.js, Chrome, Safari) เปลี่ยนแปลงเร็ว — ตรวจสอบสถานะและเวอร์ชันล่าสุดก่อนใช้งานจริง ที่ เพจ Temporal proposal หรือ MDN — ถ้ายังไม่แน่ใจว่า environment ที่ใช้รองรับหรือยัง ให้ใช้ polyfill (@js-temporal/polyfill) หรือ date-fns ไปก่อนเพื่อความปลอดภัย
Part 5: จัดรูปแบบตัวเลขด้วย Intl
นอกจากวันที่ — Intl จัดรูปแบบตัวเลขได้ด้วย
javascript
const n = 1234567.89;
// ใส่ comma คั่นหลักพัน
new Intl.NumberFormat("en-US").format(n);
// "1,234,567.89"
// แสดงเป็นเงินบาท
new Intl.NumberFormat("th-TH", {
style: "currency",
currency: "THB",
}).format(n);
// "฿1,234,567.89"
// แสดงเป็นเปอร์เซ็นต์
new Intl.NumberFormat("th-TH", { style: "percent" }).format(0.25);
// "25%"
// แบบย่อ
new Intl.NumberFormat("en", { notation: "compact" }).format(1234567);
// "1.2M"→ ใช้ Intl.NumberFormat ทุกครั้งที่แสดงตัวเลขให้ผู้ใช้เห็น — มันจัดการ comma, สกุลเงิน, locale ให้หมด
Part 6: Lab — ลงมือทำ
Lab 1: จัดการ String
ฝึกรวม string method หลายตัวเป็นสายเดียวเพื่อทำ "title case" (ขึ้นต้นแต่ละคำด้วยตัวพิมพ์ใหญ่) — toLowerCase → split → map → join:
javascript
function titleCase(str) {
return str
.toLowerCase()
.split(" ")
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
console.log(titleCase("hello WORLD from js"));
// "Hello World From Js"Lab 2: Validate ด้วย Regex
ฝึกใช้ regex ตรวจความถูกต้องของข้อมูล (email, เบอร์โทร, รหัสไปรษณีย์) โดยเก็บ pattern ไว้ใน object แล้วเลือกใช้ตามชนิด:
javascript
function validate(value, type) {
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^0\d{8,9}$/,
zipcode: /^\d{5}$/,
};
return patterns[type]?.test(value) ?? false;
}
console.log(validate("a@example.com", "email")); // true
console.log(validate("0812345678", "phone")); // true
console.log(validate("123", "zipcode")); // falseLab 3: ดึงข้อมูลด้วย Regex
ฝึกใช้ regex ดึงข้อมูลเฉพาะส่วน (วันที่, user id) ออกจากข้อความ log — รวม match กับ capturing group และ optional chaining:
javascript
const log = "Error at 2026-05-20 10:30:45 - User 12345 failed";
const date = log.match(/\d{4}-\d{2}-\d{2}/)?.[0];
const userId = log.match(/User (\d+)/)?.[1];
console.log("วันที่:", date); // "2026-05-20"
console.log("User ID:", userId); // "12345"Lab 4: นับเวลาที่ผ่านมา
💡
Intl.RelativeTimeFormatเป็น API เพิ่มเติมของIntl(เหมือนIntl.DateTimeFormatที่เรียนใน 4.5) ใช้แปลงตัวเลขเวลาเป็นข้อความเปรียบเทียบ — เช่น-5กับ"minute"→ ไทยได้"5 นาทีที่แล้ว", อังกฤษได้"5 minutes ago"— ใส่ค่าลบ = อดีต, ค่าบวก = อนาคต
Lab นี้ใช้ Intl.RelativeTimeFormat เพื่อแปลงเวลาที่ผ่านมาเป็นข้อความที่ผู้ใช้อ่านออก:
javascript
function timeAgo(date) {
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
const rtf = new Intl.RelativeTimeFormat("th", { numeric: "auto" });
// กรณี edge: ถ้าวันที่อยู่ในอนาคต (seconds < 0) หรือเพิ่งเกิดขึ้น (= 0)
if (seconds <= 0) return "เมื่อสักครู่";
if (seconds < 60) return rtf.format(-seconds, "second");
if (seconds < 3600) return rtf.format(-Math.floor(seconds / 60), "minute");
if (seconds < 86400) return rtf.format(-Math.floor(seconds / 3600), "hour");
return rtf.format(-Math.floor(seconds / 86400), "day");
}
const past = new Date(Date.now() - 5 * 60 * 1000); // 5 นาทีที่แล้ว
console.log(timeAgo(past)); // "5 นาทีที่แล้ว"
const future = new Date(Date.now() + 60_000); // 1 นาทีในอนาคต
console.log(timeAgo(future)); // "เมื่อสักครู่" — กัน edge case💡 ขยายเพิ่ม: ถ้าอยากบอก "อีก 5 นาที" สำหรับอนาคต — เปลี่ยนเงื่อนไขให้ใช้
Math.abs(seconds)แล้วเช็คทิศทาง (ถ้าseconds < 0ส่งค่าบวกเข้าrtf.formatแทนค่าลบ)
Part 7: Checkpoint
- ทำไม
"😀".lengthเป็น 2? นับให้ถูกยังไง? - String แก้ของเดิมได้ไหม?
- Regex
\d,\w,\sแทนอะไร? - Quantifier
*,+,?ต่างกันยังไง? - flag
gกับiทำอะไร? testต่างmatchยังไง?- Capturing group
( )ใช้ทำอะไร? - ทำไม
new Date(2026, 4, 20)คือพฤษภาคม ไม่ใช่เมษายน? Dateเป็น object — กระทบเรื่องการ copy ยังไง?- ทำไมควรใช้
Intlแทนจัดรูปแบบวันที่/ตัวเลขเอง?
Part 8: สรุปบทนี้
- String methods: trim, slice, split, replace, padStart — ทุกตัวสร้าง string ใหม่ (ไม่แก้เดิม)
- Unicode: emoji ใช้ 2 code unit —
"😀".lengthเป็น 2 — นับด้วย[...str] - Regex = ภาษาอธิบาย pattern —
\d \w \s, quantifier* + ?, flagsg i m - ใช้ regex:
test(เช็ค),match(ดึง),replace(แทนที่) - Date กับดัก: เดือนเริ่มจาก 0, เป็น object (copy by reference)
- ใช้
Intlจัดรูปแบบวันที่/ตัวเลขตาม locale — อย่าทำเอง - งานจริงใช้ library (date-fns) จัดการวันเวลา
บทต่อไป — Async: เรื่องที่ยากที่สุดและสำคัญที่สุดของ JavaScript