Lesson 0.1.2 — Browser vs Node.js
0. Metadata (Thông tin bài học)
| Key | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.1 — JavaScript & Runtime Fundamentals |
| Lesson | 0.1.2 — Browser vs Node.js |
| Competency | C01 — JavaScript Language, C02 — JavaScript Runtime |
| Depth Target | L2–L3 (Explain → Use) |
| Prerequisites | Lesson 0.1.1 — JavaScript là gì? |
| Estimated Cognitive Load | Low–Medium |
1. Why This Exists (Vì sao cần học)
Bạn viết một file JavaScript đơn giản:
console.log(document.title);Chạy trong Browser → hiển thị tiêu đề trang.
Chạy trong Node.js → lỗi ngay: ReferenceError: document is not defined.
Cùng một ngôn ngữ, cùng một engine (có thể cùng V8), nhưng behavior khác biệt. Đây không phải lỗi của JavaScript. Đây là hệ quả của việc cùng engine nhưng khác host environment.
Nếu không phân biệt được đâu là language core, đâu là host API, bạn sẽ:
- Copy code frontend chạy backend và ngạc nhiên khi nó lỗi.
- Dùng
localStoragetrong script Node.js. - Dùng
process.envtrong browser và thắc mắc tại sao build fail.
Lesson này xây dựng khả năng nhìn một API và biết ngay nó thuộc layer nào, runtime nào.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần biết:
- JavaScript và ECMAScript khác nhau như thế nào.
- JavaScript Engine là gì.
- Host Environment là gì.
- Runtime bao gồm những thành phần nào.
Nếu chưa rõ, quay lại Lesson 0.1.1.
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Phân biệt language feature (ECMAScript) và host API.
- Dự đoán một identifier có tồn tại trong Browser hay Node.js hay cả hai.
- Giải thích tại sao
documentkhông có trong Node.js bằng mental model runtime layer. - Phân loại ít nhất 8/10 API phổ biến vào đúng runtime.
4. Mental Model (Mô hình tư duy)
Runtime Layer Model (Mô hình tầng Runtime)
Đây là mental model pedagogical để dự đoán behavior. Nó không mô tả chi tiết implementation của V8 hay Browser internals.
Quy tắc:
- ECMAScript giống nhau ở mọi nơi.
- Engine thực thi giống nhau (nếu cùng engine).
- Host APIs khác nhau tùy môi trường.
Vì vậy, khi bạn viết Array.isArray(), nó chạy ở mọi nơi vì Array là ECMAScript.
Khi bạn viết document.querySelector(), nó chỉ chạy trong Browser vì document là host API do Browser cung cấp.
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
5.1 Language Core vs Host API (Ngôn ngữ core vs API của host)
| Layer | Ví dụ | Có ở đâu? |
|---|---|---|
| ECMAScript Language | Array, Promise, Map, typeof, function | Mọi runtime |
| Browser Host API | document, window, localStorage | Browser |
| Node.js Host API | process, require (CJS), fs, path | Node.js |
| Universal Host API | console, setTimeout, fetch¹ | Cả hai |
¹ fetch trong Node.js từ v18+.
5.2 Global Object (Đối tượng global)
// Trong Browser, global object là `window`
console.log(window === this); // true (trong global scope)
window.document; // DOM
window.localStorage; // Storage
window.fetch; // Web API5.3 globalThis — Global thống nhất
// Dùng để truy cập global object ở mọi runtime
globalThis.Array === Array; // true
globalThis.console === console; // true
// Nhưng nội dung trên globalThis khác nhau
typeof globalThis.document; // "undefined" trong Node.js
typeof globalThis.process; // "object" trong Node.js, "undefined" trong BrowserSupporting (Hỗ trợ)
- DOM (Document Object Model): API để tương tác với HTML/XML. Chỉ có ý nghĩa khi có document để thao tác. Node.js không render HTML nên không cần DOM.
- Web APIs: Tập API do WHATWG/W3C định nghĩa, Browser implement. Ví dụ:
fetch,setTimeout,localStorage. - Node.js Built-in Modules:
fs,path,http. Không phải global object mà cần import.
Awareness (Biết tồn tại)
- Deno và Bun cũng là JavaScript runtime với host APIs khác.
- Node.js v18+ hỗ trợ
fetchbản built-in, nhưng behavior có thể khác Browser (ví dụ: cookies mặc định).
Out of Scope (Không học trong bài này)
- Event Loop internals.
- V8 engine internals.
- ESM vs CJS module resolution.
fsAPI chi tiết.- DOM manipulation kỹ thuật.
6. Worked Example (Ví dụ phân tích từng bước)
Đoạn code:
console.log(typeof document);Phân tích từng bước:
Step 1 — Identify operator: typeof là toán tử cấp language (ECMAScript). Nó tồn tại ở mọi runtime.
Step 2 — Identify identifier: document là một identifier. JavaScript engine sẽ tìm binding của document trong lexical environment.
Step 3 — Check host provision:
- Trong Browser, host environment tạo ra
document(DOM API) trước khi script chạy. Engine tìm thấy → object. - Trong Node.js, host environment KHÔNG tạo ra
document. Engine không tìm thấy →undefined.
Step 4 — Apply typeof:
- Browser:
typeof document→"object". - Node.js:
typeof document→"undefined".
Kết luận: Sự khác biệt không nằm ở typeof mà nằm ở host environment có cung cấp document hay không.
7. Prediction Exercise (Bài tập dự đoán)
Đừng chạy code. Dự đoán output và giải thích tại sao.
Câu 1:
console.log(typeof window);[Đáp án & Giải thích]
Browser: "object" — window là global object do Browser cung cấp. Node.js: "undefined" — Node.js không có window.
Câu 2:
console.log(typeof process);[Đáp án & Giải thích]
Browser: "undefined" — process là Node.js host API. Node.js: "object" — process cung cấp thông tin tiến trình và environment.
Câu 3:
console.log(typeof setTimeout);[Đáp án & Giải thích]
Cả hai: "function" — setTimeout là Web API / Node.js Timer API. Cả hai runtime đều cung cấp, mặc dù implementation chi tiết khác nhau.
Câu 4:
console.log(typeof fetch);[Đáp án & Giải thích]
Browser: "function" — Web API chuẩn. Node.js: "function" (v18+) hoặc "undefined" (v16 trở xuống). fetch đã được backport vào Node.js như một Web API compatibility layer.
8. Implementation Lab (Bài lab thực hành)
Nhiệm vụ: Phân loại các API sau vào đúng cột.
| API | Language (ECMAScript) | Browser Host | Node.js Host | Cả hai |
|---|---|---|---|---|
Array | ||||
Promise | ||||
fetch | ||||
document | ||||
process | ||||
setTimeout | ||||
localStorage | ||||
Map | ||||
console | ||||
require |
[Đáp án & Giải thích]
| API | Language | Browser | Node.js | Cả hai |
|---|---|---|---|---|
Array | ✅ | |||
Promise | ✅ | |||
fetch | ✅ (Node v18+) | |||
document | ✅ | |||
process | ✅ | |||
setTimeout | ✅ | |||
localStorage | ✅ | |||
Map | ✅ | |||
console | ✅ | |||
require | ✅ |
Lưu ý quan trọng:
fetchtừng là Browser-only, nhưng giờ là Universal Host API.consolevàsetTimeoutlà Host API, không phải ECMAScript, dù cả hai runtime đều có.requirelà Node.js CJS module system (host), không phải language core.
9. Edge Cases (Các trường hợp ngoại lệ)
fetch không phải lúc nào cũng giống nhau
fetch trong Node.js v18+ tồn tại, nhưng:
// Trong Browser, fetch tự động gửi cookies cùng origin
fetch('/api');
// Trong Node.js, fetch mặc định KHÔNG gửi cookies
fetch('http://localhost/api');Triệu chứng: Code chạy trong Browser nhưng lỗi authentication trong Node.js script vì thiếu cookie.
Nguyên nhân: Cùng tên API, cùng specification gốc, nhưng host implementation có default khác nhau.
Phòng ngừa: Đọc Node.js documentation cho Web API compatibility, đừng assume behavior giống Browser 100%.
console khác nhau về output destination
console.log("hello");- Browser: In ra DevTools Console.
- Node.js: In ra stdout (terminal).
Cùng API, cùng call signature, nhưng destination và formatting khác nhau.
globalThis không đồng nghĩa "same APIs"
// Đoạn code này chạy được cả hai nơi nhưng kết quả khác
console.log(typeof globalThis.document);globalThis chỉ đảm bảo bạn trỏ đến global object, không đảm bảo nội dung trên đó giống nhau.
10. Debug Lab (Bài lab gỡ lỗi)
Tình huống: Bạn clone một project frontend và chạy unit test bằng Node.js. Test file có đoạn:
const title = document.title;Symptom (Triệu chứng): ReferenceError: document is not defined
Reproduction (Tái hiện lỗi): Chạy node test.js hoặc test runner trong Node.js environment.
Evidence (Bằng chứng):
ReferenceError: document is not defined
at Object.<anonymous> (/path/test.js:1:15)Hypothesis (Giả thuyết): Code đang truy cập Browser Host API (document) trong Node.js runtime, nơi host environment không cung cấp DOM.
Verification (Xác minh):
console.log(typeof document); // "undefined" trong Node.jsRoot Cause (Nguyên nhân gốc rễ): Nhầm lẫn giữa language feature và host API. document không phải ECMAScript built-in; nó chỉ tồn tại khi Browser host tạo ra DOM tree và expose API.
Fix (Sửa):
- Nếu là test DOM logic: dùng Browser test runner (Playwright, Puppeteer) hoặc JSDOM (mô phỏng DOM trong Node.js).
- Nếu là utility function: kiểm tra runtime trước khi truy cập.
const title = typeof document !== "undefined"
? document.title
: "default";Prevention (Phòng ngừa):
- Luôn hỏi: "API này thuộc layer nào?" trước khi dùng.
- Kiểm tra
typeofhoặc feature detection khi viết universal code.
11. Decision Exercise (Bài tập quyết định)
Context (Bối cảnh): Bạn viết một hàm utility đọc configuration:
function getConfig() {
return {
apiUrl: process.env.API_URL,
theme: localStorage.getItem("theme")
};
}Câu hỏi: Hàm này có thể chạy ở đâu? Nếu cần chạy cả Browser lẫn Node.js, bạn sẽ sửa như thế nào?
[Gợi ý & Đáp án]
Phân tích:
process.env→ Node.js only.localStorage→ Browser only.- Hàm hiện tại sẽ lỗi ở cả hai nơi nếu chạy sai runtime.
Quyết định:
function getConfig() {
return {
apiUrl: typeof process !== "undefined" ? process.env.API_URL : "/api",
theme: typeof localStorage !== "undefined"
? localStorage.getItem("theme")
: "light"
};
}Trade-off: Feature detection bằng typeof thêm code nhưng đảm bảo runtime-agnostic. Alternative là tách thành hai hàm riêng cho từng runtime.
12. Production Scenario (Tình huống thực tế)
Context: Bạn đang xây dựng ứng dụng Next.js với Server-Side Rendering (SSR). Component AnalyticsTracker sử dụng:
useEffect(() => {
window.analytics.track("page_view");
}, []);Symptom: Production build crash hoặc log lỗi window is not defined trong server logs.
Root Cause: Next.js render component trên server (Node.js runtime) trước khi gửi HTML đến browser. Trong quá trình đó, window không tồn tại.
Fix:
useEffect(() => {
if (typeof window !== "undefined") {
window.analytics.track("page_view");
}
}, []);Lesson: Host API detection không chỉ là "nice to have" — nó là requirement để code chạy an toàn trong universal JavaScript applications.
13. AI-Assisted Exercise (Bài tập với AI)
Level B — Challenge (Thử thách)
- Tự viết một đoạn code JavaScript (tối đa 10 dòng) sử dụng ít nhất 3 API khác nhau.
- Tự phân loại chúng: Language / Browser / Node.js / Universal.
- Đưa cho AI review (ChatGPT/Claude): "Hãy kiểm tra xem phân loại của tôi có đúng không."
- Verify lại bằng MDN Web Docs hoặc Node.js Documentation.
- Ghi chú lại nếu AI và documentation nói khác nhau — bạn tin cái nào? Tại sao?
Gợi ý
AI có thể nhầm lẫn giữa "có sẵn trong Node.js" và "là ECMAScript". Documentation chính thức luôn là nguồn chính xác nhất.
Đáp án tham khảo
- Bạn nghĩ
Mình viết đoạn này:
const id = setTimeout(() => {
console.log(typeof fetch, typeof process, typeof document);
}, 0);Phân loại của mình:
setTimeout→ Universal (có ở cả Browser và Node.js, nhưng thực ra là Host API, không phải ECMAScript)fetch→ Browser Host API (Node.js 18+ có nhưng vẫn là Host API)process→ Node.js Host APIdocument→ Browser Host API
- AI trả lời
AI thường nói:
"Phân loại của bạn gần đúng.
setTimeoutlà Web API / Node.js API tùy môi trường.fetchlà Web API.processlà Node.js API.documentlà Browser API. Tuy nhiên,setTimeoutcũng có thể coi là built-in của JavaScript vì nó xuất hiện ở hầu hết mọi nơi."
- So sánh
AI không sai hoàn toàn, nhưng câu "setTimeout có thể coi là built-in của JavaScript" là mơ hồ. setTimeout không nằm trong ECMAScript spec. Nó là Host API do cả Browser và Node.js cùng cung cấp. Sự thật là "có mặt ở nhiều nơi" không đồng nghĩa với "thuộc về language core".
- Điểm AI nói sai hoặc quá mơ hồ
AI dùng từ "built-in của JavaScript" hoặc "có sẵn trong JavaScript" để nói về setTimeout, fetch, console. Điều này gây hiểu nhầm rằng chúng là language feature. Thực tế, chỉ có Array, Promise, Object, function... mới thuộc ECMAScript layer. setTimeout dù phổ biến đến mấy cũng chỉ là Host API.
- Kết luận
Documentation thắng. MDN ghi rõ setTimeout là "function implemented by browsers and Node.js", không phải "ECMAScript built-in". Khi AI và docs nói khác nhau, luôn tin docs. AI tổng hợp từ nhiều nguồn và có thể nhầm lẫn giữa "phổ biến" và "chuẩn hóa".
14. Teach Back (Dạy lại)
Trong 2 phút, giải thích cho một đồng nghiệp junior:
"Tại sao cùng một file JavaScript, chạy trong Browser thì có
document, nhưng chạy trong Node.js thì lại báo lỗidocument is not defined?"
Yêu cầu:
- Có nhắc đến ECMAScript vs Host API.
- Có giải thích
documentđến từ đâu. - Có đề cập đến engine và host environment.
- Người nghe có thể dự đoán được
typeof processtrong Browser.
Mô phỏng
- Bạn nói
Tại sao cùng file JS, Browser có document mà Node.js lại báo not defined?
Vì document không nằm trong ECMAScript. ECMAScript chỉ định nghĩa những thứ như Array, function, typeof — những cái có ở mọi nơi. Còn document là Host API, do Browser cung cấp.
Khi Engine (ví dụ V8) chạy code, nó đọc đến document. Nó kiểm tra: cái này có trong ECMAScript không? Không. Vậy nó hỏi Host Environment.
- Nếu Host là Browser: "Ừ, tao có DOM, đây là
document." → chạy được. - Nếu Host là Node.js: "Tao là server, tao không có DOM, không có
document." → báo lỗi.
Nên lỗi không phải do code sai cú pháp, cũng không phải do Engine khác nhau. Cùng một Engine V8, nhưng Host khác thì API khác. Browser có DOM, Node.js có fs và process.
Nói chung: ECMAScript quy định luật chung, Host quy định có gì trong tay. Browser có document, Node.js có process. Đừng nhầm "có sẵn ở nhiều nơi" với "thuộc về language core".
💡 Dễ nhớ: Cùng một đầu bếp (Engine), cùng một cuốn sách dạy nấu (ECMAScript), nhưng vào nhà hàng Âu (Browser) thì có nguyên liệu làm pizza (
document,window), vào nhà hàng Nhật (Node.js) thì có nguyên liệu làm sushi (fs,process). Đầu bếp không đổi, sách không đổi — chỉ là nhà hàng cung cấp đồ khác nhau.
Gợi ý đánh giá bản thân
- Đồng nghiệp có tự dự đoán được
typeof processtrong Browser không? - Họ có hiểu tại sao
fetchcó ở cả hai nhưng vẫn là Host API không? - Nếu họ hỏi "vậy
consolethì sao?" — bạn giải thích được không?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Format (Hình thức đánh giá) | Depth |
|---|---|---|
| Phân biệt language và host API | Classification (Phân loại) | L2 |
| Dự đoán runtime availability | Prediction (Dự đoán) | L2 |
Giải thích document error | Explain (Giải thích) | L2 |
| Phân loại 8/10 API đúng | Use (Sử dụng mental model) | L3 |
| Debug environment mismatch | Debug (Gỡ lỗi) | L3 |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể giải thích tại sao
documentkhông tồn tại trong Node.js bằng mental model runtime layer. - [ ] Có thể dự đoán đúng behavior của
typeof <api>trong Browser và Node.js cho ít nhất 8/10 API phổ biến. - [ ] Có thể phân loại một API chưa từng gặp thành Language / Browser / Node.js / Universal bằng cách tra documentation.
- [ ] Có thể debug
ReferenceError: X is not definedvà xác định đó là host API missing hay syntax error. - [ ] Có thể viết feature detection đơn giản (
typeof x !== "undefined") để code chạy an toàn qua nhiều runtime.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): ECMAScript → Engine → Runtime → Host Environment. Bạn đã biết JavaScript runtime gồm engine + host, nhưng chưa phân biệt cụ thể host nào cung cấp cái gì.
Current (Hiện tại): Browser vs Node.js runtime differences. Bạn biết cùng engine có thể chạy cùng language core nhưng expose host APIs khác nhau. Mental model "Runtime Layer" giúp dự đoán availability của bất kỳ API nào.
Next (Tiếp theo):
- Lesson 0.1.3 — JavaScript Execution ở mức khái niệm: Source Code → Parse → Execute. Bạn sẽ thấy engine xử lý code như thế nào trước khi runtime APIs được truy cập.
- Stage 1 — Execution Model: Execution Context, Scope, Closure. Runtime layer model sẽ trở lại khi bạn hiểu engine tạo ra execution context như thế nào.
- Stage 4 — Browser Runtime: DOM, Events, Rendering Pipeline. Host APIs của Browser sẽ được đào sâu.
- Stage 8 — React Engineering: SSR/CSR boundary.
typeof windowchecks sẽ trở thành architectural pattern thay vì chỉ là feature detection.