以為 DOM 就是 HTML
錯誤認知:「我改了 HTML 就改了頁面」「HTML 和 DOM 是一樣的東西」
實際上:HTML 是靜態文字,DOM 是瀏覽器解析 HTML 後建立的記憶體樹狀結構。兩者從解析那一刻起就分道揚鑣——JavaScript 修改的是 DOM,不是 HTML 原始檔案。
<!-- 你的 HTML -->
<p id="msg">Hello</p>
<script>
document.getElementById('msg').textContent = 'World';
// DOM 改了,HTML 原始碼還是 "Hello"
// "右鍵 → 檢視原始碼" 仍然看到 "Hello"
// "右鍵 → 檢查" 看到的是 DOM,顯示 "World"
</script>為什麼重要:伺服器端渲染(SSR)輸出的是 HTML,用戶端 hydration 接管的是 DOM。理解這個差異才能理解為什麼 SSR + CSR 的狀態有時候不一致。
以為 RESTful 就是用 HTTP
錯誤認知:「我的 API 用 HTTP 所以是 RESTful」「只要用 JSON over HTTP 就算 REST」
實際上:REST(Representational State Transfer)是 Roy Fielding 在 2000 年博士論文定義的架構風格,有 6 個約束條件。其中最常被違反的是:
- Uniform Interface / HATEOAS:response 應包含下一步可用的 links,讓 client 不需要預先知道 API 結構
- Stateless:每個 request 要帶完整狀態,server 不存 session
- Resource-based URLs:
GET /users/123(資源)vsPOST /getUserById(動作)
實務上幾乎沒有人真正實作「完整 REST」,我們寫的大多數是 HTTP API 或 REST-like API——這沒有問題,但不要把它叫做 RESTful 然後在討論時跟嚴格定義的 REST 混淆。
更實際的分工:07-restful-api.md 講的是「業界常見的 HTTP API 設計慣例」,那個才是你真正在用的東西。
以為 Bundler 只是壓縮工具
錯誤認知:「Webpack / Vite 只是把 JS 壓縮、變小而已」
實際上:壓縮(minification)只是 bundler 做的最後一步。核心工作是:
- Module resolution:解析
import/require依賴樹,把幾百個 .js 檔案打成一個(或幾個) bundle - Tree shaking:靜態分析哪些 export 沒被 import,刪掉——這是
lodash-es比lodash小的原因 - Code transformation:TypeScript → JS、JSX → JS、現代語法 → 相容舊瀏覽器的語法(Babel / SWC)
- Asset pipeline:CSS、圖片、字體的處理、hash 命名(cache busting)
沒有 module bundler,瀏覽器要分別發幾百個 HTTP request 載入每個 .js 檔案——在 HTTP/1.1 時代這會讓頁面載入極慢。
以為 async 就是 Multi-thread
錯誤認知:「用了 async/await 就是多 thread 了」「Node.js 因為有 async 所以可以多核」
實際上:JavaScript 的 async/await 是在同一個 thread上的 event loop 調度——只有一個 thread,沒有任何並行計算。
// 這兩個 await 是「交錯等待」,不是「同時計算」
async function fetchData() {
const a = await fetch('/api/a'); // 等 a 回來
const b = await fetch('/api/b'); // 等 b 回來
// 總時間 = a 時間 + b 時間
}
// 真正的並行 I/O 要用 Promise.all
async function fetchDataParallel() {
const [a, b] = await Promise.all([fetch('/api/a'), fetch('/api/b')]);
// 總時間 ≈ max(a 時間, b 時間)
}Node.js 能處理高並發,是因為 I/O 等待期間 event loop 可以服務其他請求——不是因為多 thread 同時計算。要利用多核,需要 worker_threads 或 cluster。
以為 chmod 777 是「解決權限問題」
錯誤認知:「跑 chmod 777 就解決權限 denied 了」「這樣最方便,反正是自己的機器」
實際上:chmod 777 是「其他人都可以讀、寫、執行」——在生產環境這是安全漏洞,不是解法。
# 777 = rwxrwxrwx:任何人可讀可寫可執行
# 600 = rw-------:只有 owner 可讀寫(private key 應該用這個)
# 644 = rw-r--r--:owner 讀寫,其他人唯讀(config 檔案)
# 755 = rwxr-xr-x:owner 完整,其他人只能執行(公開程式)
# 正確做法:先看是誰在跑這個程式,讓那個 user 有對的權限
ls -la /path/to/file # 看現在是誰 own 這個檔
whoami # 看現在是哪個 user 在跑
chown correct-user:correct-group /path/to/file # 改 owner
chmod 644 /path/to/file # 給最小必要權限更常見的根本問題是 owner 設錯,不是 permission 太嚴——修 owner 而不是開 777。
以為 DNS 改完立刻生效
錯誤認知:「我已經改了 DNS,怎麼還連到舊的?」「等一下就好了?不應該啊,我已經改了」
實際上:DNS 有多層 cache,每一層都有自己的 TTL(Time to Live):
你的電腦 DNS cache(OS 層)
↓ 等 TTL 過期或手動清
ISP DNS resolver cache
↓ 等 TTL 過期(通常 300–3600 秒)
Recursive resolver cache(Google 8.8.8.8 等)
↓ 等 TTL 過期
如果你的舊 TTL 設了 86400 秒(24 小時),全球各地的 DNS resolver 最多要 24 小時才會看到新值。
正確做法:在做重大遷移前,先把 TTL 調低(如 300 秒),等全球 propagation 生效後再改 record,這樣改完最多 5 分鐘生效。緊急回滾也快。
本機測試新 DNS 可以:
# 查詢指定 nameserver,不走 cache
dig @8.8.8.8 example.com A
# 清本機 DNS cache(Windows)
ipconfig /flushdns
# 清本機 DNS cache(macOS)
sudo dscacheutil -flushcache以為 OAuth 是「登入方式」
錯誤認知:「OAuth 就是讓用戶用 Google / GitHub 帳號登入」
實際上:OAuth 2.0 是**授權(Authorization)協議,不是認證(Authentication)**協議。OAuth 解決的是:「我允許某個 App 用我的身份存取我的某些資源」。
「用 Google 帳號登入」背後通常是 OpenID Connect(OIDC)——基於 OAuth 2.0 加了身份認證層(id_token)。很多人說「我用 OAuth」其實是在用 OIDC。
分工:
- OAuth 2.0:「允許 Slack 讀取我的 Google Calendar」(授權)
- OIDC:「Google 向第三方 App 証明我是 [email protected]」(認證)
- SAML:企業 SSO 更常用,適合 B2B 場景,XML-based(比 OIDC 複雜但更成熟)
以為 HTTPS 就安全了
錯誤認知:「我已經有 HTTPS,網站是安全的。」
實際上:HTTPS 只保護傳輸層——你和 server 之間的資料不被第三方竊聽。但它對以下攻擊完全無效:
- XSS:惡意 JavaScript 直接在瀏覽器裡執行,加密通道不阻止它
- SQL Injection:輸入驗證問題,發生在 server 端邏輯層
- CSRF:跨站請求偽造,HTTPS 不管 cookie 如何被利用
- Broken Authentication:弱密碼、沒有 MFA,TLS 保護不了帳號
HTTPS 解決的是傳輸途中的被動竊聽,不解決 application logic 的漏洞。完整的安全需要傳輸層(TLS)+ 應用層(input validation、auth)+ 資料層(encryption at rest)各層都到位。
以為 TCP 可靠等於應用層不需要處理重複
錯誤認知:「TCP 保證送到,我不需要處理重複資料的問題。」
實際上:TCP 保證 bytes 在一個 TCP session 內不重複、有序地到達對方的 socket buffer。但這個保證在以下情況失效:
- TCP connection 中斷後 retry:客戶端發了 request,connection 斷了,客戶端不知道 server 有沒有收到,重試一次——server 可能執行了兩次
- Application 層的 load balancer:同一個 HTTP request 可能被 retry 到不同節點
- Message queue:Kafka、RabbitMQ 的 at-least-once delivery 語義,消費者可能收到重複 message
TCP 的可靠性是 byte stream 的可靠性,不是 business operation 的可靠性。Idempotency key 是應用層解決這個問題的標準做法——不是說 TCP 不可靠,而是 retry 的責任跨越了 TCP 的邊界。