Lesson 0.4.3 — Short-circuit Evaluation
0. Metadata (Thông tin bài học)
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.4 — Operators & Control Flow |
| Lesson | 0.4.3 — Short-circuit Evaluation |
| Competency | C01.4 — Control Flow |
| Depth | L3 (Use) |
| Prerequisites | 0.3.1 Truthy & Falsy, 0.3.2 Equality, 0.4.1 Operators, 0.4.2 Conditional Logic |
| Estimated Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn viết đoạn code sau để hiển thị số lượng item:
const count = 0;
const display = count || "N/A";
console.log(display); // "N/A" ❌Bạn mong đợi 0, nhưng nhận được "N/A". Tại sao?
Vì || không phải là "trả về giá trị bên trái nếu nó hợp lệ". Nó trả về giá trị bên trái nếu nó truthy. 0 là falsy, nên toán hạng bên phải được đánh giá và trả về.
Đây là một trong những bug phổ biến nhất của Junior JavaScript. Lesson này dạy bạn:
- Cách
&&,||,??thực sự hoạt động (không phải chỉ "AND/OR logic"). - Khi nào toán hạng bên phải được đánh giá — và khi nào không.
- Tại sao
||và??khác nhau và tại sao sự khác biệt đó quan trọng trong production.
Preview
Bạn sẽ học: || kiểm tra falsy (gồm 0, "", false, NaN, null, undefined), còn ?? chỉ kiểm tra nullish (null hoặc undefined). Điều này thay đổi hoàn toàn cách bạn viết default values.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học lesson này, bạn phải:
- [ ] Phân biệt được truthy và falsy (0.3.1).
- [ ] Biết cú pháp
&&,||,!(0.4.1). - [ ] Viết được
if/elsecơ bản (0.4.2). - [ ] Hiểu
undefinedvànulllà gì.
Nếu thiếu một trong các kỹ năng trên, quay lại lesson tương ứng trước.
3. Learning Objectives (Mục tiêu học tập)
Sau lesson này, bạn có thể:
- Dự đoán giá trị trả về của biểu thức
&&,||,??trong 8/10 trường hợp. - Giải thích tại sao toán hạng bên phải có thể không được đánh giá.
- Phân biệt
||và??— biết khi nào dùng cái nào. - Refactor
if/elseđơn giản thành short-circuit expression. - Nhận diện bug do nhầm lẫn falsy value (đặc biệt là
0,"",false).
4. Mental Model (Mô hình tư duy)
Hãy hình dung 3 toán tử như 3 "người gác cổng" (gatekeeper):
|| (LOGICAL OR)
[Biểu thức: A || B]
|
+-- A là TRUTHY (đủ tốt)
| └─> Trả về A (Bỏ qua B)
|
+-- A là FALSY (không đủ tốt)
└─> Trả về B
==============================================
&& (LOGICAL AND)
[Biểu thức: A && B]
|
+-- A là FALSY (không đủ tốt)
| └─> Trả về A (Bỏ qua B)
|
+-- A là TRUTHY (đủ tốt)
└─> Trả về B
==============================================
?? (NULLISH COALESCING)
[Biểu thức: A ?? B]
|
+-- A TỒN TẠI (khác null/undefined)
| └─> Trả về A (Bỏ qua B)
|
+-- A KHÔNG TỒN TẠI (null/undefined)
└─> Trả về BĐiểm then chốt
Cả ba toán tử đều trả về giá trị toán hạng, không phải true/false.
"hello" || "world" // → "hello" (không phải true)
0 || "default" // → "default" (không phải false)
"A" && "B" // → "B" (không phải true)5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Toán tử | Điều kiện đánh giá phải | Trả về khi trái... |
|---|---|---|
| `a | b` | |
a && b | a truthy | a nếu falsy, ngược lại b |
a ?? b | a null/undefined | a nếu không nullish, ngược lại b |
Supporting (Hỗ trợ)
- Assignment variants:
||=,&&=,??=— chỉ gán khi điều kiện short-circuit thỏa mãn. - Chaining nhiều toán tử:
a || b || c || d.
Awareness (Biết tồn tại)
- Optional chaining
?.(sẽ học ở Stage 2).
Out of Scope (Không học trong bài này)
- Bitwise operators (
|,&). - Implementing custom short-circuit logic.
- React conditional rendering (Stage 8).
6. Worked Example (Ví dụ phân tích từng bước)
Ví dụ 1: || chain
const result = 0 || "" || false || "found" || "skip";Bước 1 — Đánh giá 0:0 là falsy. Không đủ để quyết định cho ||. Tiếp tục.
Bước 2 — Đánh giá "":"" là falsy. Tiếp tục.
Bước 3 — Đánh giá false:false là falsy. Tiếp tục.
Bước 4 — Đánh giá "found":"found" là truthy. Short-circuit! Trả về "found". Toán hạng "skip" không bao giờ được đánh giá.
Kết quả: "found".
Ví dụ 2: && chain
const result = "A" && "B" && null && "C";Bước 1 — Đánh giá "A":"A" là truthy. && cần đánh giá phải để biết kết quả. Tiếp tục.
Bước 2 — Đánh giá "B":"B" là truthy. Tiếp tục.
Bước 3 — Đánh giá null:null là falsy. Short-circuit! Trả về null. Toán hạng "C" không bao giờ được đánh giá.
Kết quả: null.
Ví dụ 3: ?? (Nullish Coalescing)
const result = 0 ?? "default";Bước 1 — Đánh giá 0:0 không phải null và không phải undefined. Short-circuit! Trả về 0.
Kết quả: 0.
Code Review Lens
Khi review code, nếu thấy || được dùng với số hoặc chuỗi có thể rỗng, hãy hỏi: "Giá trị 0 hoặc "" có hợp lệ không? Nếu có, đây là bug tiềm ẩn."
7. Prediction Exercise (Bài tập dự đoán)
WARNING
Đừng chạy code. Đoán output và giải thích tại sao toán hạng bên phải có (hoặc không) được đánh giá.
Câu 1
const a = "hello";
const b = () => { console.log("ran"); return "world"; };
const result = a || b();
console.log(result);Dự đoán của bạn:
- Output?
b()có được gọi không? Tại sao?
Câu 2
const x = 0;
const y = 10;
const result = x && y;
console.log(result);Dự đoán của bạn:
- Output?
ycó được đánh giá không?
Câu 3
const user = { name: "", age: 0 };
const displayName = user.name || "Anonymous";
const displayAge = user.age ?? "Unknown";
console.log(displayName, displayAge);Dự đoán của bạn:
displayName?displayAge?- Tại sao chúng khác nhau?
Câu 4
const obj = null;
const result = obj && obj.name;
console.log(result);Dự đoán của bạn:
- Output?
obj.namecó được truy cập không? Tại sao điều này quan trọng khiobjcó thể lànull?
[Đáp án & Giải thích]
Câu 1:
- Output:
"hello" b()không được gọi. Vì"hello"là truthy,||short-circuit và trả về"hello"ngay lập tức.
Câu 2:
- Output:
0 ykhông được đánh giá. Vì0là falsy,&&short-circuit và trả về0ngay lập tức.
Câu 3:
displayName:"Anonymous"(vì""là falsy,||chuyển sang phải).displayAge:0(vì0không phải null/undefined,??giữ lại0).- Khác nhau vì
||kiểm tra falsy, còn??chỉ kiểm tra nullish (nullhoặcundefined).
Câu 4:
- Output:
null obj.namekhông được truy cập. Vìobjlànull(falsy),&&short-circuit và trả vềnullngay. Điều này ngăn lỗiCannot read property 'name' of null.
8. Implementation Lab (Bài lab thực hành)
Level 1 — Guided (Hướng dẫn)
Refactor đoạn code sau dùng ||:
let theme;
if (userPreference) {
theme = userPreference;
} else {
theme = "light";
}[Gợi ý]
const theme = userPreference || "light";Level 2 — Partial Scaffold (Khung mẫu)
Hoàn thành hàm getDisplayName. Nếu user tồn tại và user.name tồn tại, trả về user.name. Ngược lại trả về "Guest".
function getDisplayName(user) {
// Dùng && và ||, không dùng if/else
return /* implement here */;
}
// Test
console.log(getDisplayName({ name: "Alice" })); // "Alice"
console.log(getDisplayName({ name: "" })); // "Guest"
console.log(getDisplayName(null)); // "Guest"[Đáp án tham khảo]
function getDisplayName(user) {
return (user && user.name) || "Guest";
}Level 3 — Independent (Độc lập)
Viết hàm resolveConfig nhận object config và trả về object với các giá trị mặc định:
timeout: dùngconfig.timeout, mặc định5000.0là giá trị hợp lệ.retries: dùngconfig.retries, mặc định3.0là giá trị hợp lệ.label: dùngconfig.label, mặc định"default".""không được coi là hợp lệ (dùng||).
function resolveConfig(config) {
// Implement here
}
// Test cases
console.log(resolveConfig({ timeout: 0, retries: 0, label: "" }));
// Expected: { timeout: 0, retries: 0, label: "default" }[Đáp án tham khảo]
function resolveConfig(config) {
return {
timeout: config.timeout ?? 5000,
retries: config.retries ?? 3,
label: config.label || "default"
};
}9. Edge Cases (Các trường hợp ngoại lệ)
Edge 1: Falsy nhưng hợp lệ
const count = 0;
const display = count || "N/A"; // → "N/A" ❌Tại sao fail: 0 là falsy. Nếu 0 là giá trị hợp lệ (ví dụ: số lượng item), dùng ??.
Fix:
const display = count ?? "N/A"; // → 0 ✅Edge 2: Side effect không chạy
let ran = false;
"hello" || (ran = true);
console.log(ran); // falseTại sao: Toán hạng phải không được đánh giá vì "hello" đã truthy. Đây là feature, không phải bug — nhưng cần biết khi debug.
Edge 3: document.all (legacy falsy object) — Awareness only
Trong một số legacy browser, document.all là object nhưng lại falsy. Đây là ngoại lệ hiếm gặp, nhưng minh chứng rằng "falsy" không đồng nghĩa "empty/false logically".
Không cần nhớ chi tiết. Chỉ cần biết: falsy là một tập hợp cố định theo spec, không phải "logic thông thường".
Edge 4: Chaining ?? với || hoặc && cần ngoặc
// SyntaxError! Không hợp lệ
const x = a || b ?? c;
// Phải có ngoặc
const x = (a || b) ?? c;10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): API trả về { count: 0, label: "" }, nhưng UI hiển thị "N/A" cho cả hai field.
Reproduction (Tái hiện lỗi):
function formatResponse(data) {
return {
count: data.count || "N/A",
label: data.label || "N/A"
};
}
console.log(formatResponse({ count: 0, label: "" }));
// Actual: { count: "N/A", label: "N/A" }
// Expected: { count: 0, label: "N/A" }Evidence (Bằng chứng):
data.countlà0(falsy) →||chuyển sang"N/A".data.labellà""(falsy) →||chuyển sang"N/A".0là giá trị hợp lệ, nhưng""không hợp lệ.
Hypothesis (Giả thuyết): Dùng || cho cả hai field là sai. count cần ?? để giữ 0. label có thể giữ || vì "" thực sự cần default.
Verification (Xác minh):
function formatResponse(data) {
return {
count: data.count ?? "N/A",
label: data.label || "N/A"
};
}
console.log(formatResponse({ count: 0, label: "" }));
// → { count: 0, label: "N/A" } ✅Root Cause (Nguyên nhân gốc rễ): Nhầm lẫn giữa "falsy" và "nullish". || kiểm tra tất cả falsy values (0, "", false, NaN, null, undefined). ?? chỉ kiểm tra null và undefined.
Fix (Sửa lỗi):
- Dùng
??cho giá trị số có thể là0. - Dùng
||cho chuỗi có thể rỗng nếu chuỗi rỗng cần default. - Hoặc dùng
??cho mọi thứ nếu chỉ muốn default khi thực sự "thiếu".
Prevention (Phòng ngừa):
- Code review: hỏi "
0hoặc""có hợp lệ không?" mỗi khi thấy||. - Lint rule: cân nhắc dùng ESLint rule
prefer-nullish-coalescing.
11. Design Exercise (Bài tập thiết kế giải pháp)
INFO
Ở depth L3, bài tập thiết kế tập trung vào quyết định đơn giản giữa các lựa chọn.
Context (Bối cảnh): Bạn đang viết hàm createUser(settings).
Constraints (Ràng buộc):
settings.themecó thể là"dark","light","", hoặcundefined.settings.fontSizecó thể là12,14,16,0, hoặcundefined.settings.notificationscó thể làtrue,false, hoặcundefined.
Yêu cầu:
- Theme mặc định
"light".""nên được coi là không hợp lệ. - Font size mặc định
14.0nên được coi là hợp lệ (vì user muốn ẩn text?). - Notifications mặc định
true.falsenên được coi là hợp lệ.
Question: Với mỗi field, bạn chọn || hay ??? Giải thích.
[Đáp án tham khảo]
| Field | Operator | Lý do |
|---|---|---|
theme | || | "" là falsy và không hợp lệ, cần default. |
fontSize | ?? | 0 không phải nullish, nên vẫn hợp lệ; chỉ undefined/null mới cần default. |
notifications | ?? | false là giá trị hợp lệ; chỉ undefined/null mới cần default. |
function createUser(settings) {
return {
theme: settings.theme || "light",
fontSize: settings.fontSize ?? 14,
notifications: settings.notifications ?? true
};
}12. Production Scenario (Tình huống thực tế)
Bối cảnh: Bạn làm việc trên dashboard hiển thị số liệu từ API. Một đồng nghiệp viết:
const stats = {
totalUsers: data.totalUsers || "—",
activeUsers: data.activeUsers || "—",
conversionRate: data.conversionRate || "—"
};Vấn đề: Khi API trả về { totalUsers: 0, activeUsers: 0, conversionRate: 0 }, dashboard hiển thị "—" cho cả ba chỉ số. Product manager báo cáo bug.
Câu hỏi:
- Tại sao code trên fail?
- Bạn sửa như thế nào?
- Khi nào
"—"thực sự cần hiển thị (chỉ khi dữ liệu thực sự thiếu)?
[Đáp án tham khảo]
0là falsy, nên||chuyển sang"—".- Dùng
??cho số liệu:data.totalUsers ?? "—". "—"chỉ nên hiển thị khi giá trị lànullhoặcundefined(thiếu dữ liệu), không phải khi giá trị là0(dữ liệu hợp lệ).
13. AI-assisted Exercise (Bài tập với AI)
Level B — Challenge (Thử thách)
- Tự viết trước: Viết một đoạn giải thích ngắn (3–5 câu) về sự khác biệt giữa
||và??, và khi nào nên dùng cái nào. Tập trung vào0và"". - Đưa cho AI review: Dùng prompt: "Review my explanation of
||vs??in JavaScript. Is it accurate? What's missing?" - So sánh: AI có nhắc đến "nullish" chính xác không? AI có bỏ sót edge case nào không?
- Verify: Kiểm tra lại bằng MDN — search "Nullish coalescing operator".
Gợi ý
AI thường nói: "?? chỉ dùng khi giá trị là null hoặc undefined". Điều này đúng, nhưng AI có thể không nhấn mạnh rằng || sẽ bỏ qua tất cả falsy values (0, "", false, NaN). Hãy kiểm tra xem AI có nhắc đến 0 và "" như ví dụ cụ thể không.
[Đáp án tham khảo]
Bạn nghĩ:
||trả về phải nếu trái là falsy.??trả về phải nếu trái là null/undefined. Với số, dùng??vì0hợp lệ. Với chuỗi, tùy —""có thể hợp lệ hoặc không.AI trả lời: "
||checks for falsy values, while??checks only fornullorundefined. Use??when you want to preserve values like0or empty strings." — Đúng ở bề mặt.So sánh: AI đúng về định nghĩa, nhưng có thể không nhắc đến việc
falsevàNaNcũng bị||bỏ qua. AI cũng có thể không nhắc đến việc chaining??với||cần ngoặc.Điểm AI nói sai hoặc quá mơ hồ:
- AI có thể nói "use
??for defaults" mà không giải thích tại sao||fail với0. - AI có thể không nhắc đến assignment variants (
??=), vì chúng ít phổ biến hơn.
- AI có thể nói "use
Kết luận: Nếu bạn chỉ ra được rằng
||bỏ qua0,"",false,NaN— còn??chỉ bỏ quanull/undefined— bạn đã hiểu sâu hơn AI. Điều này sẽ quay lại ở Stage 8 (React conditional rendering) và Stage 6 (TypeScript strict null checks).
14. Teach Back (Dạy lại)
Yêu cầu: Giải thích cho một đồng nghiệp Junior trong 2 phút:
"Tại sao
0 || 5trả về5, nhưng0 ?? 5trả về0? Và tại sao điều này quan trọng khi viết code xử lý API response?"
Dùng đúng terminology: falsy, nullish, short-circuit, toán hạng bên phải.
Mô phỏng
- Bạn nói:
||kiểm tra xem giá trị bên trái có phải truthy không.0là falsy, nên||bỏ qua0và trả về5. Nhưng??chỉ kiểm tranullhoặcundefined.0không phải nullish, nên??giữ lại0. Điều này quan trọng vì API thường trả về0là một con số hợp lệ — ví dụ số lượng user. Nếu dùng||, bạn sẽ hiển thị default value thay vì0, gây bug. Dùng??thì chỉ khi dữ liệu thực sự thiếu (null/undefined) mới hiển thị default.
💡 Hãy tưởng tượng
||là "bảo vệ khỏi mọi thứ yếu ớt", còn??là "bảo vệ khỏi sự vắng mặt hoàn toàn".0yếu ớt nhưng có mặt —||từ chối nó,??chấp nhận nó.
Gợi ý đánh giá bản thân
- Đồng nghiệp có hiểu tại sao
0bị||từ chối không? - Bạn có thể dự đoán được
false ?? truevàfalse || truekhác nhau không? - Bạn có thể giải thích tại sao
NaN || 5trả về5nhưngNaN ?? 5trả vềNaNkhông?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Tiêu chí |
|---|---|---|
Dự đoán giá trị &&, ||, ?? | Prediction (Dự đoán) | Đúng ≥ 8/10 scenarios |
| Giải thích short-circuit mechanism | Explain (Giải thích) | Dùng đúng terminology, chỉ ra toán hạng nào được đánh giá |
Phân biệt || vs ?? | Classification (Phân loại) | Chọn đúng operator cho từng trường hợp |
| Refactor if/else | Implementation (Thực hành) | Code ngắn gọn, behavior giữ nguyên |
| Nhận diện falsy-value bug | Debug (Gỡ lỗi) | Tìm ra root cause trong 3/3 cases |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể dự đoán đúng giá trị trả về của biểu thức
&&,||,??trong 8/10 trường hợp. - [ ] Có thể giải thích tại sao toán hạng bên phải không được đánh giá trong ít nhất 3 scenarios khác nhau.
- [ ] Có thể phân biệt
||và??— biết khi nào0hoặc""là giá trị hợp lệ. - [ ] Có thể refactor ít nhất 2 đoạn
if/elsethành short-circuit expression mà không thay đổi behavior. - [ ] Có thể nhận diện và sửa bug do nhầm lẫn falsy value trong code xử lý API response.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Truthy & Falsy (0.3.1) → Logical Operators syntax (0.4.1) → Conditional Logic (0.4.2)
Current (Hiện tại): Short-circuit Evaluation — hiểu rằng
&&/||/??trả về giá trị toán hạng và có thể bỏ qua toán hạng bên phải.Next (Tiếp theo): Guard Clauses & Early Return (0.4.4) → React conditional rendering (Stage 8) → TypeScript strict null checks (Stage 6) → Default config patterns (Stage 9)