🧰 ของที่ใช้ตรวจในบทนี้ (รันวันที่ 27 สิงหาคม 2026)
Chrome (เอนจิน Blink) # ใช้วัด document.compatMode / characterSet
python3 -m http.server 8756 # static file server ที่ใช้ยิง curl ใส่
ผลรันทุกอันในบทนี้ก็อปมาจาก terminal และจาก DevTools ของจริง
คุณทำซ้ำได้ด้วยคำสั่งเดียว: python3 -m http.server 8756 ในโฟลเดอร์ที่มีไฟล์ HTML
1 · คุณอยู่ตรงไหนของสายนี้แล้ว
ใน บทเรียน 37 คุณเขียนโปรแกรม Go
ที่นั่งรอ request แล้วตอบกลับด้วย Content-Type: application/json กับ body ที่เป็นตัวอักษร JSON
เว็บเซิร์ฟเวอร์ที่ส่งหน้าเว็บทำเรื่องเดียวกันเป๊ะ ต่างกันแค่สองจุด:
Content-Type เป็น text/html และตัวอักษรใน body เป็น HTML แทน JSON
นี่คือของจริง — ผมยิง curl -i ใส่ static file server แล้วก็อปมาทั้งดุ้น:
$ curl -i http://localhost:8756/resume.html
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.2
Content-type: text/html
Content-Length: 260
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Joey's Resume</title>
</head>
<body>
<h1>Resume</h1>
<p>Hello, My name is Joey Wasin</p>
</body>
</html>
สังเกตให้ดี: ทั้งก้อนนี้คือ "ตัวอักษร" ไม่มีอะไรเป็นรูปภาพ ไม่มีอะไรเป็นหน้าจอ
สิ่งที่วิ่งอยู่ในสายคือ byte เรียงกัน 260 ตัว — เหมือนตอนที่ server Go ของคุณส่ง
{"status":"ok"} ออกไป
คนที่เปลี่ยน byte พวกนี้ให้กลายเป็นภาพคือ เบราว์เซอร์ ไม่ใช่ server
คุณสั่งชั้นวางหนังสือออนไลน์ สิ่งที่รถส่งของเอามาให้คือกล่องแบน ๆ กับกระดาษคู่มือ
ไม่ใช่ชั้นวางหนังสือ
คนที่ทำให้มันกลายเป็นชั้นวางหนังสือจริง ๆ คือคุณ ที่ห้องคุณ
server ส่ง คู่มือ (ไฟล์ HTML ที่เป็นตัวอักษร) · เบราว์เซอร์คือคนประกอบ
หน้าเว็บสวย ๆ ที่คุณเห็น ไม่เคยเดินทางผ่านสายเน็ตเลยสักครั้ง มันถูกประกอบขึ้นในเครื่องคุณทุกครั้งที่เปิด
ที่ไม่เหมือนกัน: คู่มือ IKEA ถูกออกแบบให้ประกอบออกมาได้ชิ้นเดียวเป๊ะ ๆ แต่ HTML ชุดเดียวกันประกอบออกมาไม่เหมือนกันได้ — จอมือถือกับจอคอม เบราว์เซอร์คนละยี่ห้อ คนที่ขยายตัวอักษรไว้ 150% ได้หน้าตาต่างกันหมด และนั่นคือเรื่องปกติ ไม่ใช่บั๊ก
2 · หนึ่งหน้าเว็บ ไม่ใช่หนึ่ง request
จุดนี้คือจุดที่คนมาจากฝั่ง backend เข้าใจผิดบ่อยที่สุด เพราะ endpoint ที่คุณเขียนมาทั้งหมด
คือ "หนึ่ง request หนึ่ง response" จบในตัว
หน้าเว็บไม่ใช่แบบนั้น — ไฟล์ HTML ที่ได้มาเป็นแค่ใบสั่งของ
พออ่านเจอบรรทัดที่ชี้ไปหาไฟล์อื่น เบราว์เซอร์จะยิง request เพิ่มเองอัตโนมัติ โดยที่คุณไม่ได้สั่ง
<!-- page.html -->
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8" />
<title>Joey's Resume</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Resume</h1>
<img src="logo.svg" alt="logo" />
<script src="app.js"></script>
</body>
</html>
เปิดหน้านี้หนึ่งครั้ง เบราว์เซอร์ยิง HTTP request ไปที่ server กี่ครั้ง?
4 ครั้ง — นี่คือรายการจริงจากแท็บ Network ของ DevTools:
GET http://localhost:8756/page.html → 200 OK
GET http://localhost:8756/style.css → 200 OK
GET http://localhost:8756/logo.svg → 200 OK
GET http://localhost:8756/app.js → 200 OK
คุณพิมพ์ URL ไปครั้งเดียว แต่ได้ request 4 ครั้ง
request แรกคือของที่คุณสั่ง · อีก 3 ครั้งเบราว์เซอร์สั่งเพิ่มเองหลังอ่าน HTML จบ
เพราะเจอ href กับ src ที่ชี้ไปหาไฟล์อื่น
ผลตามมาที่ต้องรู้ไว้ตั้งแต่วันนี้: ทุกไฟล์ที่คุณแนบเข้าไปในหน้า คือ request หนึ่งครั้งเสมอ หน้าเว็บที่แนบไฟล์ 60 ไฟล์ = 61 request ต่อคนเข้า 1 คน — และนั่นคือเหตุผลที่มีอาชีพชื่อ "performance engineer" อยู่บนโลก
Content-Type- header ที่บอกว่า body ที่ส่งมาเป็นของชนิดไหน —
application/jsonที่คุณใช้มาตลอด กับtext/htmlของหน้าเว็บ ต่างกันแค่บรรทัดนี้บรรทัดเดียว · ถาม senior ได้ว่า “endpoint นี้ตอบ Content-Type อะไร” ก่อนถามว่า “ตอบอะไรกลับมา” เพราะคำตอบแรกบอกได้เลยว่าฝั่งรับต้องเอาไปทำอะไรต่อ - subresource (ไฟล์ลูก)
- ไฟล์ที่เบราว์เซอร์โหลดตามมาเองเพราะ HTML ชี้ไปหา — CSS, รูป, ฟอนต์, สคริปต์ · พิมพ์ถาม AI ได้ว่า “list every subresource this page will request on first load” จะได้รายการที่ตรงกับแท็บ Network มากกว่าการนั่งไล่อ่านเอง
- static file server
- server ที่ไม่ได้คิดอะไรเลย มีหน้าที่หยิบไฟล์จากดิสก์ส่งออกไปตาม path ที่ขอมา · ตรงข้ามกับ API server ที่คุณเขียนมา ซึ่งประกอบ response ขึ้นใหม่ทุกครั้ง · ประโยคที่ใช้ได้จริง: “ตรงนี้ต้องการแค่ static file server ไม่ต้องมี backend”
3 · สามภาษา สามหน้าที่ — และมันแยกกันได้จริง
สไลด์สรุปไว้สั้นมากว่า HTML + CSS + JavaScript = Web แล้วให้ภาพมาสามภาพ:
ตึกที่กำลังก่อสร้าง (โครงเหล็ก ยังไม่มีผนัง) · ห้องนั่งเล่นที่ตกแต่งเสร็จแล้ว · และสวิตช์ไฟบนผนัง
สามภาพนี้ไม่ใช่ภาพประกอบสวย ๆ มันคือคำนิยามทั้งหมดของสามภาษา:
| ภาษา | ตอบคำถามว่า | ถ้าไม่มีมัน |
|---|---|---|
| HTML Structure / Content |
หน้านี้มีอะไรอยู่บ้าง และอะไรอยู่ในอะไร | ไม่มีหน้าเว็บ — ไม่มีอะไรให้แต่งตั้งแต่แรก |
| CSS Styling / Interior |
ของพวกนั้นหน้าตาเป็นยังไง วางตรงไหน สีอะไร | ยังอ่านได้ครบทุกตัวอักษร แค่หน้าตาเป็นค่าเริ่มต้นของเบราว์เซอร์ |
| JavaScript Function / Interaction |
พอผู้ใช้ทำอะไรแล้วเกิดอะไรขึ้น | หน้ายังอ่านได้ แค่กดแล้วไม่มีอะไรเปลี่ยน |
HTML คือโครงสร้าง — เสา คาน พื้น ผนัง ช่องประตู
บ้านที่มีแค่โครงยังเข้าไปเดินได้ ยังรู้ว่าห้องไหนเป็นห้องไหน แค่มันดิบ ๆ
CSS คือการตกแต่งภายใน — สีผนัง พื้นไม้ โคมไฟ ตำแหน่งโซฟา
JavaScript คือสวิตช์ไฟกับรีโมทแอร์ — ของที่ "ทำอะไรบางอย่าง" ตอนมีคนไปกด
ลำดับนี้สำคัญ: ตกแต่งภายในบ้านที่ยังไม่มีผนังไม่ได้ · ติดสวิตช์ไฟบนอากาศไม่ได้
ทุกอย่างเกาะอยู่บน HTML — นี่คือเหตุผลที่คอร์สนี้สอน HTML ก่อน
ที่ไม่เหมือนกัน: บ้านจริงถ้าไม่ทาสี มันก็ยังเป็นบ้านที่อยู่ได้ แต่หน้าเว็บที่ไม่มี CSS ยังใช้งานได้ 100% — คลิกลิงก์ได้ กรอกฟอร์มได้ ส่งข้อมูลได้ครบ โปรแกรมอ่านหน้าจอของคนตาบอดอ่านมันได้เหมือนเดิมทุกตัวอักษร ความแตกต่างระหว่าง "ไม่มี CSS" กับ "ไม่มีผนัง" ห่างกันเยอะกว่าที่การเปรียบเทียบนี้ทำให้รู้สึกมาก
พูดเฉย ๆ ไม่พิสูจน์อะไร ลองดูของจริง — นี่คือ HTML ล้วน ๆ ไม่มี CSS เลยแม้แต่บรรทัดเดียว:
<h1>Joey Wasin</h1>
<p>Software Engineer ที่ ODDS</p>
<ul>
<li>Go</li>
<li>HTML</li>
</ul>
<a href="https://example.com">ดูผลงาน</a>
ขี้เหร่ แต่ครบ — หัวข้อตัวใหญ่ ลิสต์มีจุดนำหน้า ลิงก์เป็นสีน้ำเงินขีดเส้นใต้และกดได้
ของพวกนี้ไม่ได้มาจากไหน มันคือค่าเริ่มต้นที่เบราว์เซอร์แถมมาให้ทุกแท็ก
ทีนี้เติม CSS 6 บรรทัดลงไป โดยไม่แตะ HTML แม้แต่ตัวเดียว:
<style>
h1 { color: #7c4dff; font-size: 22px; }
p { color: #666; }
ul { list-style: none; padding: 0; }
li { display: inline-block; background: #eee;
padding: 4px 10px; border-radius: 99px; }
a { color: #0a7; text-decoration: none; }
</style>
<h1>Joey Wasin</h1>
<p>Software Engineer ที่ ODDS</p>
<ul>
<li>Go</li>
<li>HTML</li>
</ul>
<a href="https://example.com">ดูผลงาน</a>
สองกล่องข้างบน HTML เหมือนกันทุกตัวอักษร ต่างกันแค่มี <style> เพิ่มมาก้อนเดียว
นี่คือคำว่า "แยกหน้าที่กัน" ของจริง — ไม่ใช่คำพูดสวย ๆ ในสไลด์
และมันแปลว่าคุณเปลี่ยนหน้าตาเว็บทั้งเว็บได้โดยไม่แตะเนื้อหาเลย
ซึ่งเป็นเหตุผลที่ปุ่ม "โหมดมืด" มีอยู่ได้
- markup language
- ภาษาที่ไม่ได้ใช้ "สั่งให้ทำ" แต่ใช้ "ติดป้ายว่านี่คืออะไร" — HTML ไม่มี
ifไม่มี loop เพราะมันไม่ใช่ภาษาโปรแกรม · ถ้ามีคนบอกว่า “เขียนโปรแกรมด้วย HTML” ให้รู้ว่าเขาใช้คำผิด และเป็นสัญญาณว่าอาจสื่อสารกันคนละเรื่องอยู่ - render / rendering engine
- ขั้นตอนที่เบราว์เซอร์เอา HTML + CSS มาคำนวณว่าอะไรควรอยู่พิกัดไหน สีอะไร แล้ววาดลงจอ · ตัวที่ทำงานนี้ใน Chrome ชื่อ Blink ใน Safari ชื่อ WebKit · พิมพ์ถาม AI ได้ว่า “why does this render differently in Safari and Chrome” คำว่า render ทำให้คำถามแคบลงกว่าคำว่า “ทำไมมันเพี้ยน”
- default styles (user-agent stylesheet)
- CSS ที่เบราว์เซอร์แถมมาให้ทุกแท็กตั้งแต่ก่อนคุณเขียนอะไร — ที่มาของ
<h1>ตัวใหญ่ และลิงก์สีน้ำเงิน · ถาม senior ได้ว่า “อันนี้เป็นสไตล์ที่เราเขียน หรือเป็น user-agent stylesheet” เวลาเจอสไตล์ที่หาต้นตอในโปรเจกต์ไม่เจอ
4 · ไฟล์ HTML ที่เล็กที่สุดที่ยังใช้งานได้จริง
สไลด์ให้โครงนี้มา และมันคือโครงที่คุณจะพิมพ์ซ้ำไปตลอดชีวิตการทำงาน
เซฟเป็นไฟล์ชื่อ resume.html แล้วดับเบิลคลิกได้เลย ไม่ต้องมี server ไม่ต้อง build อะไรทั้งนั้น:
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Joey's Resume</title>
</head>
<body>
<h1>Resume</h1>
<p>Hello, My name is Joey Wasin</p>
</body>
</html>
| บรรทัด | มีไว้ทำอะไร | ลืมแล้วเกิดอะไร |
|---|---|---|
<!DOCTYPE html> |
บอกเบราว์เซอร์ว่า "อ่านไฟล์นี้ด้วยกฎยุคปัจจุบัน" | เบราว์เซอร์สลับไปโหมดเลียนแบบของเก่า (ดูกล่องทายผลข้างล่าง) |
<html lang="th"> |
รากของทั้งเอกสาร · lang บอกภาษาของเนื้อหา |
โปรแกรมอ่านหน้าจอออกเสียงผิดภาษา · เบราว์เซอร์เสนอแปลหน้าผิด ๆ |
<head> |
ที่เก็บของที่ไม่ได้แสดงบนหน้า — ชื่อแท็บ, การเชื่อม CSS, ข้อมูลกำกับไฟล์ | เบราว์เซอร์สร้าง <head> ให้เองแล้วเก็บ meta/title เข้าไปให้
หน้าไม่พัง — แต่พังทันทีถ้าลำดับผิด เอา <h1> ไปวางก่อน
<title> เมื่อไหร่ ของที่เหลือตกไปอยู่ใน <body> หมด (ตรวจกับ Chrome แล้ว) |
<meta charset="UTF-8"> |
บอกว่าตัวอักษรในไฟล์นี้เข้ารหัสแบบไหน | ภาษาไทยกลายเป็นตัวขยะทั้งหน้า — มีผลรันจริงอยู่ข้างล่าง |
<meta name="viewport" …> |
บอกมือถือว่า "ใช้ความกว้างจอจริง อย่าย่อหน้าเว็บให้เล็กจิ๋ว" | เปิดบนมือถือแล้วตัวหนังสือเล็กเท่ามด ต้องซูมเอง |
<title> |
ชื่อที่ขึ้นบนแท็บเบราว์เซอร์และในผลค้นหา Google | แท็บโชว์ชื่อไฟล์หรือ URL ดิบ ๆ แทน |
<body> |
ทุกอย่างที่มองเห็นบนหน้าอยู่ในนี้ | เบราว์เซอร์สร้างให้เองเหมือนกัน · แต่ถ้าไม่มีอะไรอยู่ข้างในเลย ก็ไม่มีอะไรให้ดู |
ส่วนที่มองเห็นจริง ของไฟล์นั้นมีแค่สองบรรทัดใน <body>
ที่เหลือทั้งหมดเป็นงานหลังบ้าน:
<h1>Resume</h1>
<p>Hello, My name is Joey Wasin</p>
Resume
Hello, My name is Joey Wasin
ทำไมกล่องข้างบนถึงมีแค่ส่วนใน <body>?
เพราะกล่อง "ผลลัพธ์จริง" ในเว็บนี้คือหน้าเว็บซ้อนอยู่ในหน้าเว็บ (<iframe>)
มันมี <!DOCTYPE> · <html> · <head> ของตัวเองอยู่แล้ว
อยากเห็นไฟล์เต็ม ๆ ทำงานจริงต้องเซฟเป็นไฟล์แล้วเปิดเอง ซึ่งคือข้อ 1 ของภารกิจท้ายบท
<meta charset="UTF-8"> แล้วเป็นแบบนี้จริง ๆ
ผมเซฟไฟล์เดียวกันสองเวอร์ชัน (เป็น UTF-8 ทั้งคู่) ต่างกันแค่มี/ไม่มีบรรทัด meta charset
แล้วเสิร์ฟผ่าน server ที่ไม่ได้บอก charset มาใน header — ผลที่ได้จาก Chrome:
# มี <meta charset="UTF-8">
สวัสดีครับ ผมชื่อโจอี้
document.characterSet = UTF-8
# ไม่มี
สวัสดีครับ ผมชื่à¸à¹‚จà¸à¸µà¹‰
document.characterSet = windows-1252
ไฟล์ไม่ได้พัง byte ในไฟล์เหมือนเดิมเป๊ะทุกตัว —
เบราว์เซอร์แค่เดาผิดว่าจะแปล byte พวกนั้นเป็นตัวอักษรอะไร
มันเดาเป็น windows-1252 ซึ่งเป็นการเดาแบบยุค 90 ที่ยังติดมาถึงทุกวันนี้
บรรทัดเดียวนี้คือความต่างระหว่าง "เว็บภาษาไทย" กับ "เว็บที่อ่านไม่ออก"
<!-- ไฟล์ ก. -->
<!DOCTYPE html>
<html lang="th"><head><meta charset="UTF-8">
<style>body { font-size: 30px }</style></head>
<body><table><tr><td id="c">ในตาราง</td></tr></table></body></html>
<!-- ไฟล์ ข. — เหมือนกันทุกตัวอักษร แต่ลบบรรทัด <!DOCTYPE html> ออก -->
อ่านค่า font-size ที่คำนวณได้จริงของ <td> ทั้งสองไฟล์ ได้เท่าไหร่?
# ไฟล์ ก. (มี doctype)
compatMode = CSS1Compat td font-size = 30px
# ไฟล์ ข. (ไม่มี doctype)
compatMode = BackCompat td font-size = 16px
ไฟล์ ข. ไม่ได้ error อะไรเลย ไม่มีคำเตือน ไม่มีขีดแดง หน้ายังขึ้นปกติ
แต่ <td> ของมันไม่รับ font-size ที่สืบทอดมาจาก body —
เพราะเบราว์เซอร์สลับไปใช้กฎของยุคก่อนปี 2000 ที่ตารางไม่สืบทอดฟอนต์
สิ่งที่ผมทดสอบแล้วพบว่า "ไม่ต่าง" ก็มี: ผมลองวัด offsetWidth
ของกล่องที่มี width:200px; padding:20px; border:5px ในทั้งสองโหมด —
ได้ 250px เท่ากันทั้งคู่ ทั้งที่ตำราเก่าหลายเล่มบอกว่าโหมดเก่าจะได้ 200
Chrome ปี 2026 ปิดช่องว่างตรงนั้นไปแล้ว แต่ยังไม่ได้ปิดทุกช่อง อย่างที่เห็นในเรื่องตาราง
บทเรียนที่แท้จริงจึงไม่ใช่ "ลืม doctype แล้วพังยังไง" — เพราะรายการนั้นเปลี่ยนทุกปี แต่คือ: ไม่มี doctype = คุณเข้าไปอยู่ในโหมดที่ไม่มีใครทดสอบอีกแล้ว พิมพ์บรรทัดเดียวแล้วออกมาซะ
- element · tag · attribute
<p class="x">สวัสดี</p>ทั้งก้อนคือ element ·<p>กับ</p>คือ tag เปิดและปิด ·class="x"คือ attribute · ใช้คำว่า element เวลาพูดถึง “ของชิ้นนั้น” และ tag เวลาพูดถึง “ตัวหนังสือที่พิมพ์” แยกสองคำนี้ให้ออกแล้วอ่านเอกสาร MDN ง่ายขึ้นทันที- doctype
- บรรทัดแรกสุดของไฟล์ที่ประกาศว่าเอกสารนี้เป็น HTML ยุคปัจจุบัน · ไม่ใช่ tag ไม่ใช่ element เป็นคำประกาศเฉย ๆ · ประโยคที่ใช้ได้จริง: “หน้านี้หลุด standards mode รึเปล่า เช็ค doctype ให้หน่อย”
- standards mode / quirks mode
- สองโหมดการตีความ CSS ของเบราว์เซอร์ · เช็คได้ทันทีด้วยการพิมพ์
document.compatModeใน Console — ได้CSS1Compatคือปกติ ได้BackCompatคือหลุดไปโหมดเก่า · พิมพ์ถาม AI ได้ว่า “document.compatMode returns BackCompat — what could cause that”
5 · เอา CSS มาต่อกับ HTML — มีสามทาง ใช้จริงทางเดียว
CSS ไม่ได้ลอยไปเกาะ HTML เอง คุณต้องบอกให้เบราว์เซอร์รู้ ว่ามีสไตล์อยู่ตรงไหน มีสามวิธี และควรรู้จักทั้งสามเพื่ออ่านโค้ดคนอื่นออก ถึงแม้จะใช้จริงแค่วิธีเดียว
| วิธี | เขียนยังไง | ใช้เมื่อไหร่ |
|---|---|---|
| 1. ไฟล์แยก external |
<link rel="stylesheet" href="style.css"> ใน <head> |
ค่าเริ่มต้นที่ควรใช้เสมอ — ไฟล์เดียวคุมได้ทุกหน้า และเบราว์เซอร์แคชไว้ใช้ซ้ำได้ |
| 2. ฝังในไฟล์ internal |
<style> … </style> ใน <head> |
ไฟล์เดียวจบ เช่นตัวอย่างสอน · หน้าเดียวโดด ๆ · อีเมล HTML |
| 3. ติดที่ตัว element inline |
<p style="color: red"> |
เกือบไม่ควรใช้ · แก้แล้วต้องไล่หาทีละจุด และไปทับสไตล์อื่นแบบเงียบ ๆ |
ไฟล์แยก = ประกาศติดบอร์ดกลางที่ทุกชั้นอ่านร่วมกัน แก้แผ่นเดียวเปลี่ยนทั้งบริษัท
ฝังในไฟล์ = ประกาศติดหน้าห้องประชุมห้องเดียว ใช้ได้เฉพาะห้องนั้น
inline = เดินไปกระซิบบอกพนักงานทีละคน ว่าวันนี้ใส่เสื้อสีอะไร
วิธีที่สามได้ผลทันทีและชัดเจนที่สุด — จนกระทั่งวันที่ต้องเปลี่ยนกฎ แล้วคุณจำไม่ได้ว่ากระซิบใครไปบ้าง
ที่ไม่เหมือนกัน: ในออฟฟิศจริง คำสั่งจากปากหัวหน้ากับประกาศบนบอร์ดขัดกันเมื่อไหร่
ยังเถียงกันได้ว่าอันไหนใหญ่กว่า
แต่ใน CSS มีกฎตายตัวว่าใครชนะ ไม่มีการเถียง และเกือบทุกครั้ง
style="" คือฝ่ายที่ชนะประกาศบนบอร์ด (มีทางเดียวที่บอร์ดชนะกลับได้ — เดี๋ยวบอกท้ายหัวข้อนี้)
— ซึ่งฟังดูดี จนกระทั่งคุณเป็นคนที่พยายามแก้บอร์ดแล้วหน้าไม่ยอมเปลี่ยน
วิธีที่ 1 ต้องมีไฟล์จริงสองไฟล์ กล่องตัวอย่างในบทนี้ทำให้ดูไม่ได้ (มันเป็นไฟล์เดียวโดด ๆ ต่อไฟล์ภายนอกไม่ได้)
— ไปลองเองในภารกิจข้อ 3
ส่วนวิธีที่ 2 กับ 3 ดูได้เลย นี่คือทั้งสองวิธีอยู่ในหน้าเดียวกัน:
<style>
.box { border: 2px solid #7c4dff; padding: 10px;
border-radius: 6px; color: #7c4dff; }
</style>
<p class="box">ผมได้สไตล์จากบล็อก style ข้างบน</p>
<p class="box" style="color: crimson; border-color: crimson">
ผมมี class เดียวกัน แต่ style ที่ติดตัวผมมาชนะ
</p>
ผมได้สไตล์จากบล็อก style ข้างบน
ผมมี class เดียวกัน แต่ style ที่ติดตัวผมมาชนะ
ย่อหน้าสองอันมี class="box" เหมือนกัน แต่ได้สีคนละสี
นี่คือครั้งแรกที่คุณเห็น "กฎสองข้อชนกันแล้วต้องมีคนแพ้" ในโลก CSS
ซึ่งเป็นเรื่องที่บทหลัง ๆ จะลงรายละเอียดทั้งบท
ตอนนี้จำแค่ว่า: สไตล์ที่ติดกับตัว element ชนะสไตล์ที่เขียนไว้ที่อื่น
(มีข้อยกเว้นอยู่หนึ่งอย่างชื่อ !important ซึ่งจะเจอในบทหลัง)
และนั่นคือเหตุผลที่มันเป็นวิธีที่คนเกลียดที่สุด — มันแก้ทับของคนอื่นได้โดยที่คนอื่นไม่รู้ตัว
เป้าหมายไม่ใช่ "ทำหน้าเว็บให้สวย" — เป้าหมายคือทำให้ทั้งสามอย่างในบทนี้เกิดขึ้นกับมือ ทั้งอันที่ทำงาน และอันที่พัง ถ้าข้ามข้อ 4 กับ 5 ไป ภารกิจนี้จะไม่เหลืออะไรเลย
- ไฟล์แรก: สร้างโฟลเดอร์ใหม่ → ไฟล์
resume.html→ พิมพ์โครงขั้นต่ำจากหัวข้อ 4 ด้วยมือ ห้ามก็อป (พิมพ์เองรอบเดียวจำได้นานกว่าอ่านสิบรอบ) → ใส่ชื่อจริงของคุณใน<h1>และแนะนำตัวหนึ่งบรรทัดใน<p>เป็นภาษาไทย → ดับเบิลคลิกไฟล์เพื่อเปิดในเบราว์เซอร์ - ยืนยันว่าอยู่ในโหมดที่ถูก: กด F12 เปิด DevTools → แท็บ Console
→ พิมพ์
document.compatMode→ ต้องได้"CSS1Compat"
ถ้าได้"BackCompat"แปลว่าบรรทัด<!DOCTYPE html>ของคุณหาย หรือมีอะไรอยู่ข้างหน้ามัน - แยก CSS ออกเป็นไฟล์: สร้าง
style.cssข้าง ๆ กัน ใส่กฎอะไรก็ได้สองสามข้อ (เช่นh1 { color: teal }) → เชื่อมด้วย<link rel="stylesheet" href="style.css">ใน<head>→ รีเฟรช สีต้องเปลี่ยน
เปิดแท็บ Network ของ DevTools แล้วรีเฟรชอีกครั้ง — ต้องเห็น 2 รายการ คือresume.htmlกับstyle.css(นี่คือหัวข้อ 2 เกิดขึ้นตรงหน้าคุณ) - ทำให้ภาษาไทยพังโดยตั้งใจ: ลบบรรทัด
<meta charset="UTF-8">ออก → รีเฟรช
ถ้าเปิดจากไฟล์ตรง ๆ (file://) แล้วมันยังไม่พัง ถือว่าปกติ — เบราว์เซอร์เดาถูกพอดี ให้ลองใหม่ผ่าน server ด้วยpython3 -m http.server 8000แล้วเปิดhttp://localhost:8000/resume.html
จดไว้ว่าคุณเห็นอะไร แล้วใส่บรรทัดนั้นกลับ - ทำให้ doctype พังโดยตั้งใจ: ลบ
<!DOCTYPE html>ออก → รีเฟรช → พิมพ์document.compatModeใน Console อีกครั้ง
สังเกตว่าหน้ายังดูปกติทุกอย่าง — นี่คือประเด็นทั้งหมด: บั๊กประเภทนี้ ไม่ประกาศตัว มันรอ
แล้วใส่กลับ - เขียนสรุปสามบรรทัด ไว้ท้ายไฟล์ในคอมเมนต์
<!-- … -->: ข้อ 4 ทำให้เกิดอะไร · ข้อ 5 ทำให้เกิดอะไร · และข้อไหน "น่ากลัวกว่า" ในความเห็นคุณ พร้อมเหตุผลหนึ่งประโยค
ข้อ 5 น่ากลัวกว่ามาก
ข้อ 4 (ลืม charset) ตะโกนใส่หน้าคุณทันที — เห็นตัวขยะเต็มจอ
แก้ภายในสิบวินาที ไม่มีทางหลุดขึ้น production
ข้อ 5 (ลืม doctype) เงียบสนิท หน้าดูปกติทุกอย่าง คุณส่งงาน ผ่านรีวิว ขึ้น production
แล้วสามเดือนต่อมามีคนบอกว่า "ตารางในหน้านั้นฟอนต์แปลก ๆ นะ" แล้วทีมนั่งหาสามชั่วโมง
บั๊กที่แสดงอาการทันทีคือบั๊กที่ถูกที่สุดในชีวิต — บั๊กที่รอ คือบั๊กที่แพงที่สุด
6 · เช็คความเข้าใจ
1. server ส่งไฟล์ HTML ก้อนหนึ่งกลับมาที่เบราว์เซอร์ — ตอนที่ byte ชุดนั้นวิ่งอยู่ในสายเน็ต มันคืออะไร?
ผลจริงจาก curl -i ในหัวข้อ 1 ยืนยันข้อนี้: response body คือตัวอักษร 260 ตัว
ที่คุณอ่านออกด้วยตาเปล่า และ Content-Type: text/html แค่บอกว่า
"ตัวอักษรพวกนี้ควรถูกตีความแบบ HTML"
ตัวเลือกที่ 1 คือความเข้าใจผิดที่พบบ่อยที่สุด — ถ้าจริง เว็บจะเปลี่ยนขนาดตามจอไม่ได้เลย
และ Ctrl+F หาคำในหน้าก็ทำไม่ได้
ตัวเลือกที่ 2 ผิดตรงคำว่า "พิกัด" — server ไม่รู้เลยว่าจอคุณกว้างเท่าไหร่
การคำนวณพิกัดเกิดในเครื่องคุณล้วน ๆ
ตัวเลือกที่ 4 ใกล้เคียงแต่ผิดชนิด: HTML ไม่ใช่ชุดคำสั่ง มันคือการติดป้ายว่า
"อันนี้คือหัวข้อ อันนี้คือย่อหน้า" ส่วนจะวาดยังไงเบราว์เซอร์ตัดสินเอง
จุดที่ต้องจำ: server ส่ง "ความหมาย" ไม่ได้ส่ง "ภาพ" —
ทุกครั้งที่หน้าเว็บแสดงผลเพี้ยนเฉพาะบางเครื่อง ให้เริ่มหาที่ฝั่งเบราว์เซอร์ก่อนเสมอ
2. หน้าเว็บหน้าหนึ่งอ้างถึงไฟล์ CSS 1 ไฟล์ รูป 3 รูป และสคริปต์ 2 ไฟล์ — เปิดหน้านี้หนึ่งครั้งเกิด HTTP request กี่ครั้ง?
นับตรง ๆ: 1 (HTML) + 1 (CSS) + 3 (รูป) + 2 (สคริปต์) = 7
ในหัวข้อ 2 ผมรันของจริงกับหน้าที่อ้าง 3 ไฟล์ แล้วได้ 4 รายการในแท็บ Network ตรงตามสูตรนี้
ตัวเลือกที่ 1 กับ 4 คิดว่ามีการ "มัดรวม" ซึ่งไม่มีในโปรโตคอล —
HTTP รู้จักแค่ "ขอหนึ่งอย่าง ได้หนึ่งอย่าง" เหมือน endpoint ที่คุณเขียนมาทั้งหมด
ตัวเลือกที่ 3 ลืมนับตัวหน้าเอง ซึ่งก็เป็น request หนึ่งครั้งเต็ม ๆ ไม่ต่างจากไฟล์อื่น
จุดที่ต้องจำ: ทุกไฟล์ที่คุณเพิ่มเข้าไปในหน้า = อีกหนึ่ง request ต่อผู้ใช้หนึ่งคนเสมอ
ตัวเลขนี้คือสิ่งแรกที่ต้องดูเวลามีคนบอกว่า "เว็บช้า"
3. เพื่อนส่งไฟล์ HTML มาให้ช่วยดู หน้าแสดงผลปกติทุกอย่าง แต่พิมพ์ document.compatMode ใน Console แล้วได้ "BackCompat" — แปลว่าอะไร?
ผลรันจริงในหัวข้อ 4: ไฟล์เดียวกันสองเวอร์ชัน ต่างกันแค่บรรทัด doctype →
ได้ CSS1Compat กับ BackCompat ตามลำดับ
และ <td> ในเวอร์ชันหลังไม่รับ font-size ที่สืบทอดมา (16px แทน 30px)
ตัวเลือกที่ 1 ผิดชนิดของปัญหา — CSS ที่พังจะถูกข้ามเป็นรายบรรทัด ไม่เกี่ยวกับโหมดของเอกสาร
ตัวเลือกที่ 2 ผิดตรงที่คิดว่าโหมดขึ้นกับวิธีเปิด — โหมดมาจากเนื้อไฟล์อย่างเดียว
เปิดยังไงก็ได้ค่าเดิม
ตัวเลือกที่ 3 กลับหัวกลับหาง: เบราว์เซอร์ใหม่ต่างหากที่ยังต้องรองรับโหมดเก่านี้
เพื่อไม่ให้เว็บอายุ 25 ปีพังหมดโลก
จุดที่ต้องจำ: "หน้าดูปกติ" ไม่ใช่หลักฐานว่าไม่มีปัญหา —
document.compatMode คือการเช็คหนึ่งวินาทีที่ควรทำทุกครั้งที่รับไฟล์ HTML แปลกหน้ามาดูแล
4. ทีมขอให้เปลี่ยนสีปุ่มทั้งเว็บ 40 หน้า จากน้ำเงินเป็นเขียว — วิธีแนบ CSS แบบไหนที่ทำให้งานนี้เป็นฝันร้าย?
inline คือการ "กระซิบทีละคน" ตามการเปรียบเทียบในหัวข้อ 5 — ปุ่ม 300 ตัวคือ 300 จุดที่ต้องแก้
และถ้าพลาดไปตัวเดียวจะไม่มีอะไรเตือนคุณเลย
ซ้ำร้ายกว่านั้น: ต่อให้คุณไปเพิ่มกฎสีเขียวในไฟล์กลางเรียบร้อย
ปุ่มที่มี style="" ก็ยังเป็นสีน้ำเงินอยู่ดี เพราะมันชนะกฎกลาง
(เห็นได้จากกล่องผลลัพธ์จริงในหัวข้อ 5 ที่ย่อหน้าสองอันมี class เดียวกันแต่คนละสี)
ตัวเลือกที่ 2 กลับข้าง — ไฟล์แยกคือกรณีที่ง่ายที่สุด แก้ที่เดียวจบทั้ง 40 หน้า
ตัวเลือกที่ 3 อ้างเรื่องแคชผิดทาง: <style> ฝังอยู่ในตัวหน้า HTML
มันจึงถูกส่งใหม่พร้อมหน้าทุกครั้ง ไฟล์แยกต่างหากที่ถูกแคช
(และแก้ได้ด้วยการเปลี่ยนชื่อไฟล์ ไม่ใช่ปัญหาที่แก้ไม่ได้)
จุดที่ต้องจำ: เลือกวิธีแนบ CSS ด้วยคำถามว่า
"วันที่ต้องแก้ ต้องไปแก้กี่ที่" ไม่ใช่ "วันนี้พิมพ์อันไหนเร็วกว่า"
💬 ติดตรงไหนถามได้ — โดยเฉพาะคำถามประเภท
"ทำไมโค้ดผมเหมือนในบทเป๊ะแต่ผลไม่เหมือน" ซึ่งเป็นคำถามที่ดีที่สุดของบทนี้
เพราะคำตอบมักอยู่ที่ charset หรือ path ของไฟล์ ไม่ได้อยู่ที่ตัวโค้ด
ถ้าอยากถาม AI ให้ตรงจุด แปะโค้ดพร้อมประโยคนี้: "this is plain HTML/CSS, no framework —
tell me what the browser does with it line by line"
อ้างอิง
- MDN — How the web works — อ่านแค่หัวข้อ So what happens, exactly? ซึ่งเป็นลิสต์สั้น ๆ ที่ไล่ตั้งแต่พิมพ์ URL จนหน้าขึ้น · เอาไว้เทียบกับหัวข้อ 1 และ 2 ของบทนี้
- MDN —
<meta>— อ่านหัวข้อ charset กับ viewport สองอันนี้พอ ที่เหลือในหน้านั้นยังไม่ต้องสน · จะได้รู้ว่าทำไมสองบรรทัดนี้ถึงอยู่ในทุกไฟล์บนโลก - MDN — Quirks Mode and Standards Mode —
อ่านสองย่อหน้าแรก เพื่อรู้ที่มาว่าทำไมโหมดนี้ถึงยังมีอยู่ในปี 2026 ·
แล้วจำแค่ว่า
document.compatModeคือวิธีเช็ค - MDN —
Document.compatMode— หน้าสั้นมาก อ่านตารางค่าที่คืนได้ อย่างเดียวก็พอ (BackCompat/CSS1Compat) · เอาไว้ใช้ตอนทำภารกิจข้อ 2 - HTML Standard (WHATWG) — The DOCTYPE — อ่านสี่ประโยคแรกของหัวข้อ 13.1.1 อย่างเดียว ซึ่งพูดตรง ๆ ว่า doctype ต้องมี "ด้วยเหตุผลทางประวัติศาสตร์" และถ้าไม่มี เบราว์เซอร์มักสลับไปโหมดเรนเดอร์อีกแบบ ที่ไม่ตรงกับ spec · เป็นตัวอย่างที่ดีของการเปิด spec ตัวจริงแทนการเชื่อบล็อก
- บทเรียน 37 — HTTP server ตัวแรกด้วย
net/http— อีกฝั่งของสายเดียวกัน · กลับไปดูตรงที่ตั้งContent-Typeแล้วเปลี่ยนเป็นtext/htmlดู จะเห็นว่า server ที่คุณเขียนเองส่งหน้าเว็บได้ทันที