โหมดมืด
บทที่ 02 — ตัวแปร และ ชนิดข้อมูล
บทนี้คือรากฐานของทุกอย่าง — ตัวแปร (variable) คือ "กล่องเก็บข้อมูล" และชนิดข้อมูล (type) คือ "ชนิดของสิ่งที่เก็บ"
อ่านจบบทนี้คุณจะ:
- เข้าใจ
let,const,var— ใช้อันไหนเมื่อไหร่ และทำไม - รู้จักชนิดข้อมูลทั้งหมดของ JavaScript
- เข้าใจความต่างที่สำคัญมากระหว่าง "primitive" กับ "object"
- เข้าใจการแปลงชนิดข้อมูล (ที่เป็นต้นตอของบั๊กเยอะ)
ใช้เวลา 2-3 ชั่วโมง — อ่านช้า ๆ ลองโค้ดตาม
Part 1: ตัวแปรคืออะไร
1.1 แนวคิด
ตัวแปร (variable) = กล่องที่มีชื่อ ไว้เก็บข้อมูล
ลองนึกภาพตู้เก็บของที่มีป้ายชื่อ — เราเอาของใส่กล่อง ติดป้าย แล้วเรียกใช้ของผ่านป้ายชื่อ
javascript
let age = 25;บรรทัดนี้แปลว่า: "สร้างกล่องชื่อ age แล้วใส่เลข 25 ลงไป"
ต่อมาเราเรียกใช้ค่าผ่านชื่อ age:
javascript
console.log(age); // 25
console.log(age + 5); // 301.2 ทำไมต้องมีตัวแปร
ลองนึกถ้าไม่มีตัวแปร — เราต้องเขียนค่าตรง ๆ ทุกครั้ง:
javascript
console.log("ราคา 100 บาท ลด 10% เหลือ " + (100 - 100 * 0.1));ถ้าราคาเปลี่ยน ต้องแก้ทุกที่ที่มีเลข 100 — พลาดง่าย
ใช้ตัวแปร — แก้ที่เดียว:
javascript
let price = 100;
let discount = 0.1;
console.log("ราคา " + price + " บาท ลด " + (discount * 100) + "% เหลือ " + (price - price * discount));Part 2: 3 คำสั่งประกาศตัวแปร — let, const, var
JavaScript มี 3 คำสำหรับ "สร้างตัวแปร" — และนี่คือเรื่องที่มือใหม่ต้องเข้าใจให้ชัด
javascript
let x = 10; // ตัวแปรที่เปลี่ยนค่าได้
const y = 20; // ตัวแปรที่เปลี่ยนค่าไม่ได้
var z = 30; // วิธีเก่า — เลิกใช้2.1 const — ใช้เป็นค่าเริ่มต้น (default)
const ย่อมาจาก "constant" (ค่าคงที่) — ตัวแปรที่ ตั้งค่าแล้วเปลี่ยนไม่ได้
javascript
const pi = 3.14159;
pi = 3; // ❌ TypeError: Assignment to constant variable
// (กำหนดค่าใหม่ให้ตัวแปร const ไม่ได้)ถามว่า — ถ้าเปลี่ยนค่าไม่ได้ จะมีประโยชน์อะไร?
ตอบ — เพราะ ค่าส่วนใหญ่ในโปรแกรมไม่ควรเปลี่ยน การใช้ const ป้องกันไม่ให้เราเผลอเปลี่ยนค่าโดยไม่ตั้งใจ → bug น้อยลง อ่านโค้ดง่ายขึ้น (เห็น const ก็รู้ว่าค่านี้นิ่ง)
กฎทอง: ใช้
constเป็นค่าเริ่มต้นเสมอ — เปลี่ยนเป็นletเฉพาะตอนที่ต้องเปลี่ยนค่าจริง ๆ เท่านั้น
2.2 let — ใช้เมื่อค่าต้องเปลี่ยน
let = ตัวแปรที่เปลี่ยนค่าได้
javascript
let count = 0;
count = count + 1; // ได้ — count กลายเป็น 1
count = 100; // ได้ — count กลายเป็น 100
count++; // ได้ — count กลายเป็น 101 (++ คือการเพิ่มค่าทีละ 1 — เรียนละเอียดบทที่ 03)ใช้ let กับค่าที่ต้องเปลี่ยนระหว่างโปรแกรมทำงาน — เช่น ตัวนับ, ผลลัพธ์ที่สะสมไปเรื่อย ๆ, สถานะที่เปลี่ยน
2.3 ⚠️ const ไม่ได้ทำให้ object เปลี่ยนแปลงไม่ได้ทั้งหมด
นี่เป็นจุดที่มือใหม่เข้าใจผิดบ่อยมาก — ตั้งใจอ่านดี ๆ
const แค่ห้าม "เปลี่ยนกล่องไปชี้ของใหม่" — แต่ "ของข้างใน" ยังแก้ได้:
javascript
const user = { name: "Alice" };
user.name = "Bob"; // ✅ ได้! — แก้ของข้างใน object
user.age = 25; // ✅ ได้! — เพิ่มของข้างใน object
console.log(user); // { name: "Bob", age: 25 }
user = { name: "Carol" }; // ❌ Error! — เปลี่ยนให้กล่องชี้ object ใหม่ทำไมเป็นแบบนี้? — เพราะ const user หมายถึง "กล่อง user จะชี้ไปที่ object เดิมตลอด" — แต่ object เดิมนั้น แก้ข้างในได้
💡 เปรียบเทียบ:
constเหมือน "ป้ายชื่อบ้านที่ถอดไม่ได้" คุณเปลี่ยนป้ายให้ชี้บ้านหลังอื่นไม่ได้ แต่คุณยังเข้าไปจัดเฟอร์นิเจอร์ในบ้านหลังนั้นได้
(ถ้าอยากให้ object เปลี่ยนแปลงไม่ได้จริง ๆ ใช้ Object.freeze() — เรียกครั้งเดียวแล้วทุก property "ถูกแช่แข็ง":
javascript
const config = Object.freeze({ host: "localhost", port: 8080 });
config.port = 3000; // ❌ TypeError ใน strict mode / ESM
// (ESM เป็น strict mode อัตโนมัติ — หนังสือเล่มนี้ใช้ ESM
// เรียนละเอียดเรื่อง strict mode ในบทที่ 05)หมายเหตุ: ถ้าโค้ดรันใน "sloppy mode" (CommonJS หรือ <script> แบบเก่าที่ไม่มี "use strict") การพยายามแก้จะ เงียบเฉย ๆ ไม่ throw — แต่ทั้งบทนี้และทั้งหนังสือเราอยู่บน ESM ซึ่ง strict โดยอัตโนมัติ. เรียนละเอียดบทที่ 06)
2.4 var — วิธีเก่า อย่าใช้
var คือวิธีประกาศตัวแปรแบบเก่า (ก่อน ES6) — มันมีพฤติกรรมแปลก ๆ ที่ทำให้เกิดบั๊ก
ปัญหาหลักของ var:
ปัญหา 1: ไม่เคารพ block (ขอบเขต { })
javascript
if (true) {
var x = 10;
}
console.log(x); // 10 — x หลุดออกมานอก if ได้!
if (true) {
let y = 10;
}
console.log(y); // ❌ Error — y อยู่แค่ใน if (ถูกต้อง)let/const "อยู่ในกรอบ" ของ { } — แต่ var หลุดออกมาได้ ทำให้โค้ดคาดเดายาก
ปัญหา 2: ประกาศซ้ำได้โดยไม่ error
javascript
var a = 1;
var a = 2; // ✅ ไม่ error — แต่อันตราย เผลอประกาศซ้ำไม่รู้ตัว
let b = 1;
let b = 2; // ❌ Error — เตือนเรา (ดี)กฎทอง: ปี 2026 อย่าใช้
varเด็ดขาด — ใช้constกับletเท่านั้น. เรียนรู้จักvarไว้แค่เพื่ออ่านโค้ดเก่าออก
2.5 Block Scope — ขอบเขตของตัวแปร
{ } (ปีกกา) สร้าง "block" — let/const ที่ประกาศใน block จะอยู่แค่ใน block นั้น:
javascript
{
const secret = "ความลับ";
console.log(secret); // "ความลับ" — ใช้ได้ (อยู่ใน block)
}
console.log(secret); // ❌ ReferenceError: secret is not defined
// (ออกนอก block แล้ว ตัวแปรหายไป)นี่เป็นเรื่องดี — มันทำให้ตัวแปร "ไม่รั่ว" ออกไปที่อื่น โค้ดเป็นระเบียบ
2.6 Temporal Dead Zone (TDZ — เขตห้ามชั่วคราว) — ใช้ตัวแปรก่อนประกาศ
ลองดู:
javascript
console.log(name); // ❌ ReferenceError: Cannot access 'name' before initialization
// (ใช้ตัวแปรก่อนบรรทัดประกาศไม่ได้)
let name = "Alice";JavaScript ไม่อนุญาตให้ใช้ตัวแปร let/const ก่อนบรรทัดที่ประกาศ — ช่วงตั้งแต่ต้น block (หรือขอบเขต function/module) จนถึงบรรทัดประกาศ เรียกว่า TDZ (ช่วงที่ตัวแปรมีอยู่ในระบบแล้ว แต่ยังใช้ไม่ได้)
นี่เป็นเรื่องดี — มันบังคับให้เรา "ประกาศตัวแปรก่อนใช้" ซึ่งทำให้โค้ดอ่านง่าย
(ตรงนี้เป็นอีกข้อที่ var แย่กว่า — var ใช้ก่อนประกาศได้แต่ได้ค่า undefined ซึ่งทำให้บั๊กหายาก)
Part 3: ชนิดข้อมูลของ JavaScript
ข้อมูลในโปรแกรมมีหลาย "ชนิด" — ตัวเลขก็แบบหนึ่ง ข้อความก็อีกแบบ. JavaScript แบ่งชนิดข้อมูลเป็น 2 กลุ่มใหญ่:
- Primitive types — ข้อมูล "พื้นฐาน" (เลข, ข้อความ, true/false, ฯลฯ)
- Object types — ข้อมูลที่ "ซับซ้อน" (object, array, function)
ความต่างระหว่าง 2 กลุ่มนี้สำคัญมาก — เราจะคุยกันใน Part 6
3.1 เช็คชนิดข้อมูลด้วย typeof
JavaScript มีคำสั่ง typeof ไว้ถามว่า "ค่านี้เป็นชนิดอะไร":
javascript
typeof 42 // "number"
typeof "hello" // "string"
typeof true // "boolean"เราจะใช้ typeof ตลอดบทนี้เพื่อพิสูจน์เรื่องต่าง ๆ
Part 4: Primitive Types — 7 ชนิด
JavaScript มี primitive type ทั้งหมด 7 ชนิด — มาดูทีละตัว
4.1 Number — ตัวเลข
ในภาษาอื่นมักแยกเลขจำนวนเต็ม (int) กับเลขทศนิยม (float) — แต่ JavaScript มีชนิดเลขแค่ชนิดเดียว คือ number ใช้ได้ทั้งหมด:
javascript
let age = 25; // จำนวนเต็ม
let price = 99.99; // ทศนิยม
let temp = -10; // ติดลบ
let big = 1_000_000; // ใส่ _ คั่นได้ เพื่ออ่านง่าย (= 1000000)ค่าพิเศษของ number:
javascript
Infinity // อนันต์ (เช่น 1/0 — JS ไม่ throw error เหมือนภาษาอื่น)
-Infinity // ลบอนันต์
NaN // "Not a Number" — ผลของการคำนวณที่ผิดพลาดNaN เกิดเมื่อทำเลขที่ "ทำไม่ได้":
javascript
let result = "abc" * 2;
console.log(result); // NaN — เอาข้อความมาคูณ ทำไม่ได้⚠️ ปัญหาทศนิยม — เรื่องที่ต้องรู้
ลองพิมพ์ใน Console:
javascript
0.1 + 0.2คุณคาดว่าได้ 0.3 ใช่ไหม? — แต่ JavaScript ตอบ 0.30000000000000004
นี่ไม่ใช่บั๊กของ JavaScript — มันเป็นข้อจำกัดของ "การเก็บทศนิยมในคอมพิวเตอร์" (ทุกภาษาที่ใช้ floating-point เป็นเหมือนกัน) — คอมพิวเตอร์เก็บเลขเป็นเลขฐาน 2 และ 0.1 ในฐาน 2 เป็นทศนิยมไม่รู้จบ เลยปัดเศษนิดหน่อย
วิธีรับมือ:
javascript
// ❌ อย่าเทียบทศนิยมตรง ๆ
0.1 + 0.2 === 0.3 // false
// ✅ ปัดก่อนเทียบ
Math.round((0.1 + 0.2) * 100) / 100 === 0.3 // true
// หรือใช้ toFixed สำหรับแสดงผล
(0.1 + 0.2).toFixed(2) // "0.30"→ ถ้าทำงานกับเงิน — เก็บเป็น "สตางค์" (จำนวนเต็ม) แทนการเก็บเป็น "บาท.สตางค์" (ทศนิยม) เพื่อเลี่ยงปัญหานี้
4.2 String — ข้อความ
ข้อความเขียนได้ 3 แบบ:
javascript
let a = "double quotes"; // เครื่องหมายคำพูดคู่
let b = 'single quotes'; // เครื่องหมายคำพูดเดี่ยว
let c = `backticks`; // backtick — พิเศษ (อธิบายข้างล่าง)"..." กับ '...' ใช้เหมือนกันทุกอย่าง — เลือกอันใดอันหนึ่งให้สม่ำเสมอ
Template Literal — backtick พิเศษยังไง
`...` (backtick) มี 2 ความสามารถพิเศษ:
1. แทรกตัวแปรลงในข้อความ ด้วย ${ }
javascript
let name = "Alice";
let age = 25;
// แบบเก่า — ต้อง + ต่อกันยุ่ง ๆ
let msg1 = "ชื่อ " + name + " อายุ " + age + " ปี";
// แบบใหม่ — ใช้ ${ } อ่านง่ายกว่ามาก
let msg2 = `ชื่อ ${name} อายุ ${age} ปี`;ภายใน ${ } ใส่ expression อะไรก็ได้:
javascript
`ปีหน้าอายุ ${age + 1} ปี` // "ปีหน้าอายุ 26 ปี"
`ชื่อตัวใหญ่: ${name.toUpperCase()}` // "ชื่อตัวใหญ่: ALICE"2. เขียนข้อความหลายบรรทัดได้
javascript
let poem = `บรรทัดที่หนึ่ง
บรรทัดที่สอง
บรรทัดที่สาม`;กฎทอง: ใช้ template literal (backtick) เป็นค่าเริ่มต้นในงานสมัยใหม่ — โดยเฉพาะเวลาต้องแทรกตัวแปร
คำสั่งจัดการ String ที่ใช้บ่อย
javascript
let s = "Hello, World!";
s.length // 13 — จำนวนตัวอักษร
s.toUpperCase() // "HELLO, WORLD!"
s.toLowerCase() // "hello, world!"
s[0] // "H" — ตัวอักษรตำแหน่งที่ 0
s.includes("World") // true — มีคำว่า "World" ไหม
s.startsWith("Hello") // true
s.endsWith("!") // true
s.indexOf("World") // 7 — "World" อยู่ตำแหน่งที่เท่าไหร่
s.slice(0, 5) // "Hello" — ตัดเอาตำแหน่ง 0 ถึง 5
s.replace("World", "JS") // "Hello, JS!"
s.trim() // ตัดช่องว่างหน้า-หลังออก
s.split(", ") // ["Hello", "World!"] — แยกเป็น array
s.at(-1) // "!" — ตัวอักษรจากท้าย (ES2022: -1 = ตัวสุดท้าย)
s.replaceAll("l", "L") // "HeLLo, WorLd!" — แทนที่ทุกตัวที่พบ⚠️ String เปลี่ยนแปลงไม่ได้
javascript
let s = "hello";
s[0] = "H"; // ❌ TypeError ใน strict mode / ESM
// (Cannot assign to read only property '0' of string)
console.log(s); // ถ้าโค้ดรันถึงตรงนี้ได้ (sloppy mode เก่า) — ยัง "hello"
s = s.toUpperCase(); // ✅ ต้อง assign กลับ
console.log(s); // "HELLO"💡 หนังสือเล่มนี้ใช้ ESM (
"type": "module") ซึ่งเป็น strict mode อัตโนมัติ — การพยายามแก้ตัวอักษรใน string จะ throw error ทันที (V8/Node.js/Chrome ทำได้แน่นอน). ใน "sloppy mode" (CommonJS แบบเก่าหรือ<script>ที่ไม่มี"use strict") มันจะเงียบเฉย ๆ ทำให้ดีบักยาก — อีกเหตุผลที่แนะนำ ESM
คำสั่ง string ทุกตัว ไม่แก้ของเดิม — มัน "สร้างข้อความใหม่" แล้วคืนกลับมา ถ้าอยากเก็บผลต้อง assign
4.3 Boolean — จริง/เท็จ
มีแค่ 2 ค่า:
javascript
let isActive = true;
let isDeleted = false;ใช้กับการตัดสินใจ (if, loop) — เรียนบทที่ 04
Truthy / Falsy — ค่าที่ "เหมือนจริง" และ "เหมือนเท็จ"
JavaScript มีแนวคิดสำคัญ — ทุกค่าสามารถถูกมองเป็น "จริง" หรือ "เท็จ" ได้ แม้ไม่ใช่ boolean
ค่าที่ถือว่า "เท็จ" (falsy) หลัก ๆ มี 8 ตัว:
javascript
false
0 // เลขศูนย์
-0 // ลบศูนย์ (มือใหม่ยังไม่ต้องสนใจ)
0n // BigInt ศูนย์ (เลขจำนวนเต็มใหญ่มาก ๆ — ดูหัวข้อ 4.7)
"" // ข้อความว่าง
null
undefined
NaN💡 มือใหม่โฟกัส 6 ตัวหลักก่อน:
false,0,"",null,undefined,NaN— 6 ตัวนี้เจอบ่อยในงานจริง ส่วน-0กับ0nเป็นกรณีพิเศษที่แทบไม่ได้ใช้ตอนเริ่มต้น (เก็บไว้รู้ว่า "มี" ก็พอ ยังไม่ต้องจำ)
📝 เกร็ดสำหรับคนรู้เยอะ: จริง ๆ มีค่าที่ 9 ที่ falsy คือ
document.all(object เก่าของเบราว์เซอร์ยุค Internet Explorer หรือ IE — มีเฉพาะใน browser ไม่มีใน Node.js) — เป็น quirk (พฤติกรรมแปลกที่หลงเหลือ) ที่ spec (ข้อกำหนดภาษา) อนุญาตไว้เพื่อ backward-compat (ให้เข้ากันได้กับโค้ดเก่า) ไม่ต้องสนใจ — เห็นแค่ตัวอย่างว่ากฎมีข้อยกเว้นแปลก ๆ บ้าง
ทุกค่าอื่นถือว่า "จริง" (truthy) — รวมถึงค่าที่อาจดูเหมือนเท็จ:
javascript
"0" // truthy! (เป็น string ที่ไม่ว่าง)
" " // truthy! (มี space ก็ไม่ว่าง)
[] // truthy! (array ว่างก็ truthy)
{} // truthy! (object ว่างก็ truthy)ใช้แปลงเป็น boolean ด้วย Boolean():
javascript
Boolean("hello") // true
Boolean("") // false
Boolean(0) // false
Boolean(42) // trueจำ 6 falsy values หลัก ให้แม่น (false, 0, "", null, undefined, NaN) — มันสำคัญมากเวลาเขียน if (บทที่ 03-04) ส่วน -0 กับ 0n รู้ว่ามีก็พอ ยังไม่ต้องจำ
4.4 undefined — "ยังไม่มีค่า"
undefined แปลว่า "ตัวแปรนี้มีอยู่ แต่ยังไม่ได้ใส่ค่า":
javascript
let x;
console.log(x); // undefined — ประกาศแล้วแต่ไม่ได้ใส่ค่าJavaScript ใส่ undefined ให้อัตโนมัติเมื่อ:
- ประกาศตัวแปรแต่ไม่ assign ค่า
- เรียก property ของ object ที่ไม่มี
- function ที่ไม่ return อะไร
4.5 null — "ตั้งใจให้ว่าง"
null แปลว่า "ตั้งใจให้ค่านี้ว่างเปล่า" — เราเป็นคนใส่เอง:
javascript
let selectedUser = null; // ยังไม่มีใครถูกเลือก (เราตั้งใจ)undefined ต่าง null ยังไง
ทั้งคู่หมายถึง "ไม่มีค่า" — แต่ความหมายต่างกัน:
undefined= "JavaScript ยังไม่ได้ใส่ค่า" (เกิดอัตโนมัติ)null= "เราตั้งใจใส่ว่างไว้" (developer ใส่เอง)
javascript
typeof undefined // "undefined"
typeof null // "object" — ⚠️ พฤติกรรมเพี้ยนตั้งแต่ JS รุ่นแรก ๆ
// เคยพยายามแก้แต่จะทำให้โค้ดเก่าพังเยอะ เลยถูกบรรจุเป็นส่วนหนึ่งของ spec ECMAScript ไปแล้ว(อย่าตกใจที่ typeof null เป็น "object" — มันเป็นเรื่องในตำนานที่ทุกคนรู้กัน เป็นข้อยกเว้นที่ต้องจำว่า "เช็ค null ด้วย === null เท่านั้น อย่าพึ่ง typeof")
4.6 Symbol — ค่าที่ไม่ซ้ำใคร
Symbol สร้างค่าที่ "ไม่มีวันซ้ำ" — ใช้ในงานขั้นสูง:
javascript
const id1 = Symbol("id");
const id2 = Symbol("id");
id1 === id2 // false — แม้ชื่อเหมือนกัน ก็ไม่เท่ากันตัวอย่างที่ใช้ Symbol จริง — ทำ "key ลับ" ใน object ไม่ให้ชนกับ key อื่น:
javascript
const HIDDEN = Symbol("hidden");
const user = { name: "Alice", [HIDDEN]: "secret-token" };
// คนอื่นเอาคีย์ "hidden" มาแทนของเราไม่ได้ เพราะ Symbol ไม่ซ้ำใช้จริงน้อยสำหรับมือใหม่ — ส่วนใหญ่เจอตอนใช้ library ขั้นสูง (เช่น Symbol.iterator ทำของให้ for...of ใช้ได้). เรียนเพิ่มบทที่ 06
4.7 BigInt — เลขใหญ่มาก ๆ
ชนิด number (Number type) เก็บเลขจำนวนเต็มได้ แม่นยำ ใหญ่สุดประมาณ 9 พันล้านล้าน (9 × 10^15 หรือ Number.MAX_SAFE_INTEGER = 9,007,199,254,740,991) — เกินตัวเลขนี้ number ยังเก็บได้อยู่ แต่บางค่าจำนวนเต็มจะเริ่มไม่แม่นยำ (สูญเสีย precision กลายเป็นเลขปัดผิด) ไม่ใช่ error ที่ทำให้โปรแกรมพัง. ถ้าต้องการเลขจำนวนเต็มใหญ่กว่านั้นแบบไม่จำกัดและแม่นยำเป๊ะ ใช้ BigInt (เก็บได้ใหญ่เท่าที่ RAM ไหว):
javascript
const huge = 9007199254740993n; // ใส่ n ต่อท้าย
const huge2 = BigInt("123456789012345678901234567890");ใช้น้อยสำหรับมือใหม่ — รู้จักไว้ว่ามี
⚠️
BigIntกับnumberผสมกันในการคำนวณไม่ได้โดยตรง:1n + 1→ TypeError — ต้องแปลงก่อน:1n + BigInt(1)หรือNumber(1n) + 1
Part 5: Object Types — ข้อมูลที่ซับซ้อน
ทุกอย่างที่ไม่ใช่ primitive ทั้ง 7 ชนิด = object (รวม Array, Function, Date, Map, Set, RegExp, ฯลฯ — ทั้งหมดเป็น object พิเศษที่ขยายจาก object พื้นฐาน)
javascript
typeof {} // "object"
typeof [] // "object" — array ก็เป็น object ชนิดหนึ่ง
typeof function(){} // "function" — function เป็น object พิเศษ
typeof new Date() // "object"5.1 Object — เก็บข้อมูลเป็นคู่ key-value
Object คือวิธีจับกลุ่มข้อมูลที่เกี่ยวข้องกันไว้เป็นก้อนเดียว ในรูปแบบ "คู่ key-value" (เช่น คน 1 คนมี name, age, isActive) — เข้าถึงค่าด้วยจุด (user.name) หรือวงเล็บเหลี่ยม (user["age"]):
javascript
const user = {
name: "Alice", // key "name" → value "Alice"
age: 25,
isActive: true,
};
user.name // "Alice" — เข้าถึงด้วย . (จุด)
user["age"] // 25 — เข้าถึงด้วย [ ](เรียนละเอียดบทที่ 06)
5.2 Array — เก็บข้อมูลเป็นรายการ
javascript
const fruits = ["แอปเปิล", "กล้วย", "ส้ม"];
fruits[0] // "แอปเปิล" — ตัวแรก (เริ่มนับจาก 0)
fruits.length // 3(เรียนละเอียดบทที่ 07)
Part 6: Primitive ต่าง Object ยังไง — เรื่องสำคัญที่สุดของบทนี้
นี่คือแนวคิดที่ทำให้มือใหม่งงและเขียนบั๊กบ่อยที่สุด — ตั้งใจอ่านมาก ๆ
6.1 Primitive — เก็บแบบ "ค่า" (copy by value — ก๊อปค่าออกมาเป็นอิสระ)
เมื่อ assign primitive จากตัวแปรหนึ่งไปอีกตัว — มัน copy ค่า:
javascript
let a = 10;
let b = a; // b ได้ "สำเนาของค่า" 10
b = 20; // เปลี่ยน b
console.log(a); // 10 — a ไม่เปลี่ยน (เป็นคนละค่ากันแล้ว)
console.log(b); // 20a กับ b เป็นอิสระต่อกัน — เปลี่ยนตัวหนึ่งไม่กระทบอีกตัว
6.2 Object — เก็บแบบ "การอ้างอิง" (copy by reference — ก๊อปที่อยู่อ้างอิง สองตัวแปรชี้ของชิ้นเดียวกัน)
เมื่อ assign object — มัน copy "ที่อยู่" (reference) ไม่ใช่ copy ตัว object:
javascript
let user1 = { name: "Alice" };
let user2 = user1; // user2 ได้ "ที่อยู่เดียวกัน" กับ user1
user2.name = "Bob"; // เปลี่ยนผ่าน user2
console.log(user1.name); // "Bob" — user1 เปลี่ยนตาม!ทำไม user1 เปลี่ยนตาม? — เพราะ user1 กับ user2 ชี้ไปที่ object ตัวเดียวกัน
ดูภาพ:
💡 เปรียบเทียบ: primitive เหมือน "ถ่ายเอกสาร" — ได้สำเนาแยกกัน. object เหมือน "ให้ที่อยู่บ้าน" — 2 คนได้ที่อยู่เดียวกัน ใครเข้าไปจัดบ้านก็เห็นเหมือนกัน
6.3 ผลที่ตามมา — เปรียบเทียบ object
javascript
{} === {} // false — คนละ object (คนละที่อยู่)
[] === [] // false
let x = { v: 1 };
let y = x;
x === y // true — ที่อยู่เดียวกันobject 2 ตัวที่ "หน้าตาเหมือนกัน" ไม่ได้ถือว่าเท่ากัน — JavaScript เทียบที่ "ที่อยู่" ไม่ใช่ "เนื้อหา"
6.4 การ copy object — shallow (ตื้น) vs deep (ลึก)
ถ้าอยากได้ object ใหม่จริง ๆ (ไม่ใช่ที่อยู่เดิม):
javascript
const original = { name: "Alice", age: 25 };
// copy แบบตื้น (shallow copy)
const copy1 = { ...original }; // ใช้ ... (spread — กระจาย property ของ object เดิมเข้า object ใหม่ เรียนละเอียดบทที่ 06)
const copy2 = Object.assign({}, original);
copy1.name = "Bob";
console.log(original.name); // "Alice" — ไม่กระทบ ✅แต่ shallow copy มีข้อจำกัด — ถ้า object มี object ซ้อนข้างใน:
javascript
const original = { name: "Alice", address: { city: "Bangkok" } };
const copy = { ...original };
copy.address.city = "Tokyo";
console.log(original.address.city); // "Tokyo" — ⚠️ กระทบ! เพราะ address ข้างในยัง share กันถ้าต้องการ copy ลึกทุกชั้น (deep copy) — ใช้ structuredClone() (built-in มาตั้งแต่ Node.js 17+ และเบราว์เซอร์สมัยใหม่ทุกตัว ตั้งแต่ปี 2022):
javascript
const deepCopy = structuredClone(original);
deepCopy.address.city = "Tokyo";
console.log(original.address.city); // "Bangkok" — ไม่กระทบ ✅→ จำ: { ...obj } = copy ตื้น (พอใช้ส่วนใหญ่), structuredClone(obj) = copy ลึกทั้งหมด
⚠️ ข้อจำกัดของ
structuredClone: ไม่รองรับ function (ไม่ถูก clone), DOM node, WeakMap/WeakSet — สำหรับ object ทั่วไป (plain object, array, Date, Map, Set) ใช้ได้ปกติ
Part 7: การแปลงชนิดข้อมูล
บ่อยครั้งเราต้องแปลงชนิดข้อมูล — เช่นรับ input จากผู้ใช้ (เป็น string) แล้วอยากเอามาคำนวณ (เป็น number)
7.1 แปลงเป็น Number
javascript
Number("42") // 42
Number("3.14") // 3.14
Number(" 42 ") // 42 — ตัดช่องว่างให้
Number("abc") // NaN — แปลงไม่ได้
Number("") // 0 — ⚠️ ข้อความว่างกลายเป็น 0 (น่าแปลกใจ)
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
// วิธีลัด — ใส่ + ข้างหน้า
+"42" // 42
// parseInt / parseFloat — ยืดหยุ่นกว่า (ดึงเลขจากข้อความที่ปน)
parseInt("42px") // 42 — ดึงเฉพาะเลข
parseFloat("3.14 บาท") // 3.14⚠️ ระวัง Number("") ได้ 0 — เวลา validate ฟอร์ม ถ้าผู้ใช้ไม่กรอกอะไร แล้วเราแปลงเป็นเลข จะกลายเป็น 0 โดยไม่ตั้งใจ
7.2 แปลงเป็น String
แปลงค่าชนิดอื่นให้เป็นข้อความด้วย String() หรือวิธีลัด (.toString(), template literal, ต่อกับ string ว่าง) — ใช้บ่อยเวลาแสดงผลหรือประกอบข้อความ:
javascript
String(42) // "42"
String(true) // "true"
String(null) // "null"
String([1, 2, 3]) // "1,2,3"
// วิธีลัด
(42).toString() // "42"
`${42}` // "42" — template literal
"" + 42 // "42" — ต่อกับ string ว่าง7.3 แปลงเป็น Boolean
แปลงค่าเป็น true/false ด้วย Boolean() หรือวิธีลัด !! (อัศเจรีย์คู่) โดยยึดกฎ truthy/falsy จาก Part 4.3 — ใช้บ่อยเวลาเช็คเงื่อนไข:
ใช้กฎ truthy/falsy จาก Part 4.3:
javascript
Boolean("hello") // true
Boolean("") // false
Boolean(0) // false
Boolean(42) // true
// วิธีลัด — ใช้ !! (อัศเจรีย์คู่)
!!"hello" // true
!!0 // false7.4 การแปลงอัตโนมัติ (Implicit Coercion) — ต้องระวัง
จากบทที่ 00 — JavaScript แปลงชนิดให้อัตโนมัติบางครั้ง ทำให้เกิดผลแปลก ๆ:
javascript
"5" + 3 // "53" — + เห็น string เลยต่อข้อความ
"5" - 3 // 2 — - ใช้กับเลขเท่านั้น เลยแปลง "5" เป็น 5
"5" * "2" // 10
true + 1 // 2 — true แปลงเป็น 1
[] + [] // "" — array ว่างแปลงเป็น string ว่าง💡 เกร็ด:
[] + {}ได้"[object Object]"แต่{} + []ได้0— ผลต่างกันเพราะ{}ในตำแหน่งเริ่มต้น statement ถูก parser มองเป็น block ว่าง ไม่ใช่ object literal (สับสนมาก — นี่เป็นเหตุผลหนึ่งที่ต้องแปลงชนิดเองเสมอ)
วิธีรับมือ: แปลงชนิดเองให้ชัดเจนเสมอ — อย่าพึ่งการแปลงอัตโนมัติ
javascript
// ❌ พึ่งการแปลงอัตโนมัติ — เสี่ยง
let total = userInput + 10;
// ✅ แปลงเองให้ชัด
let total = Number(userInput) + 10;Part 8: ตั้งชื่อตัวแปร
8.1 กฎ
JavaScript มีกฎตายตัวว่าตั้งชื่อตัวแปรอย่างไรได้บ้าง — ห้ามขึ้นต้นด้วยตัวเลข, ห้ามมีขีดกลาง, ห้ามใช้คำสงวน (เช่น let) แต่ขึ้นต้นด้วย _ หรือ $ ได้ มาดูตัวอย่างที่ได้/ไม่ได้:
javascript
let userName; // ✅ ได้
let _private; // ✅ ได้ — ขึ้นต้นด้วย _
let $element; // ✅ ได้ — ขึ้นต้นด้วย $
let user2; // ✅ ได้ — มีตัวเลข (แต่ห้ามขึ้นต้นด้วยเลข)
let 2user; // ❌ ขึ้นต้นด้วยเลขไม่ได้
let user-name; // ❌ มีขีดกลางไม่ได้
let let; // ❌ ใช้คำสงวนไม่ได้8.2 ธรรมเนียมการตั้งชื่อ
นอกจากกฎที่ภาษาบังคับ ยังมี "ธรรมเนียม" ที่นักพัฒนายึดร่วมกันเพื่อให้โค้ดอ่านง่าย:
camelCase("เนินอูฐ" — ตัวแรกเล็ก คำต่อไปขึ้นต้นด้วยตัวใหญ่ ดูเหมือนหลังอูฐ เช่นfirstName) สำหรับตัวแปรและฟังก์ชันPascalCase(ทุกคำขึ้นต้นด้วยตัวใหญ่ เช่นUserAccount) สำหรับ classUPPER_SNAKE_CASE(ตัวใหญ่ทั้งหมด คั่นด้วย_เช่นMAX_RETRIES) สำหรับค่าคงที่สำคัญ
javascript
// camelCase — สำหรับตัวแปรและ function (ตัวแรกเล็ก คำต่อไปตัวใหญ่)
let firstName;
let isActive;
let totalPrice;
// PascalCase — สำหรับ class (ทุกคำขึ้นต้นด้วยตัวใหญ่)
class UserAccount {}
// UPPER_SNAKE_CASE — สำหรับค่าคงที่สำคัญ
const MAX_RETRIES = 3;
const API_URL = "https://...";8.3 ตั้งชื่อให้สื่อความหมาย
javascript
// ❌ ชื่อไม่สื่อ
let x = 86400;
let d = users.filter(u => u.a);
// ✅ ชื่อสื่อความหมาย
let secondsPerDay = 86400;
let activeUsers = users.filter(user => user.isActive);ชื่อตัวแปรที่ดี = โค้ดที่อ่านเข้าใจได้โดยไม่ต้องมี comment
Part 9: Lab — ลงมือทำ
Lab 1: ทายผลลัพธ์
เปิด Console แล้วทายก่อนพิมพ์ว่าได้อะไร:
javascript
typeof 42
typeof "hello"
typeof true
typeof undefined
typeof null
typeof {}
typeof []
"5" + 3
"5" - 3
0.1 + 0.2 === 0.3
Number("")
Boolean("0")
Boolean([])Lab 2: พิสูจน์ primitive vs object
ลองรันโค้ดนี้เพื่อเห็นกับตาว่า primitive (เช่น number) ก๊อปค่าแยกกันอิสระ ส่วน object จะแชร์การอ้างอิงเดียวกัน (แก้ตัวหนึ่งกระทบอีกตัว) จุดนี้คือจุดที่มือใหม่สับสนบ่อยที่สุด:
javascript
// primitive — อิสระต่อกัน
let a = 5;
let b = a;
b = 99;
console.log("a =", a, "/ b =", b); // a = 5 / b = 99
// object — share กัน
let obj1 = { count: 0 };
let obj2 = obj1;
obj2.count = 99;
console.log("obj1.count =", obj1.count); // 99 — กระทบ!
// แก้ด้วย copy
let obj3 = { count: 0 };
let obj4 = { ...obj3 }; // { ...obj3 } คือ spread operator — กระจาย property ของ obj3 ออกมา (เรียนละเอียดบทที่ 03)
obj4.count = 99;
console.log("obj3.count =", obj3.count); // 0 — ไม่กระทบLab 3: เครื่องคิดเลขส่วนลด
javascript
const price = 1200;
const discountPercent = 15;
const discountAmount = price * (discountPercent / 100);
const finalPrice = price - discountAmount;
console.log(`ราคาเต็ม: ${price} บาท`);
console.log(`ส่วนลด: ${discountPercent}% = ${discountAmount} บาท`);
console.log(`ราคาสุทธิ: ${finalPrice} บาท`);Part 10: Checkpoint
constต่างletยังไง? ควรใช้อันไหนเป็นค่าเริ่มต้น?- ทำไม
const user = {...}แล้วuser.name = "Bob"ถึงทำได้? - ทำไมไม่ควรใช้
var? - Block scope คืออะไร? Temporal Dead Zone คืออะไร?
- Primitive type มีกี่ชนิด? อะไรบ้าง?
0.1 + 0.2 === 0.3ได้ผลอะไร? ทำไม?- Falsy values หลัก 6 ตัวคืออะไร? (แล้วมีอีก 2 ตัวพิเศษที่ไม่ต้องจำตอนเริ่มต้น)
undefinedต่างnullยังไง?- Primitive copy by value / Object copy by reference — ต่างกันยังไง?
{ ...obj }ต่างstructuredClone(obj)ยังไง?
Part 11: สรุปบทนี้
- ตัวแปร = กล่องเก็บข้อมูลที่มีชื่อ
constเป็นค่าเริ่มต้น — เปลี่ยนเป็นletเมื่อต้องเปลี่ยนค่า — ไม่ใช้varconstกับ object — เปลี่ยน "ที่ชี้" ไม่ได้ แต่แก้ข้างใน object ได้- 7 primitive types: number, string, boolean, undefined, null, symbol, bigint
- ทุกอย่างที่เหลือ = object (รวม array, function)
numberมีปัญหาทศนิยม (0.1 + 0.2 !== 0.3) — ระวัง- Falsy หลัก 8 ตัว:
false, 0, -0, 0n, "", null, undefined, NaN— ที่เหลือ truthy (มีdocument.allเป็นข้อยกเว้นแปลก ๆ ตัวที่ 9) - Primitive copy by value (อิสระ) — Object copy by reference (share ที่อยู่)
- แปลงชนิดเองให้ชัดเจน — อย่าพึ่งการแปลงอัตโนมัติ
บทต่อไป — Operators: เครื่องหมายต่าง ๆ ที่ใช้คำนวณและเปรียบเทียบ