Skip to content

Lesson 0.4.3 — Short-circuit Evaluation ​

Bài 0.4.3 — Short-circuit Evaluation
&&, ||, ?? và cách chúng thực sự hoạt động • 20 phút
0:00 / 0:00

0. Metadata (Thông tin bài học) ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.4 — Operators & Control Flow
Lesson0.4.3 — Short-circuit Evaluation
CompetencyC01.4 — Control Flow
DepthL3 (Use)
Prerequisites0.3.1 Truthy & Falsy, 0.3.2 Equality, 0.4.1 Operators, 0.4.2 Conditional Logic
Estimated Cognitive LoadMedium

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:

js
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/else cơ bản (0.4.2).
  • [ ] Hiểu undefined và null là 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ể:

  1. Dự đoán giá trị trả về của biểu thức &&, ||, ?? trong 8/10 trường hợp.
  2. Giải thích tại sao toán hạng bên phải có thể không được đánh giá.
  3. Phân biệt || và ?? — biết khi nào dùng cái nào.
  4. Refactor if/else đơn giản thành short-circuit expression.
  5. 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):

text

|| (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.

js
"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ảiTrả về khi trái...
`ab`
a && ba truthya nếu falsy, ngược lại b
a ?? ba null/undefineda 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 ​

js
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 ​

js
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) ​

js
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 ​

js
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 ​

js
const x = 0;
const y = 10;
const result = x && y;
console.log(result);

Dự đoán của bạn:

  • Output?
  • y có được đánh giá không?

Câu 3 ​

js
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 ​

js
const obj = null;
const result = obj && obj.name;
console.log(result);

Dự đoán của bạn:

  • Output?
  • obj.name có được truy cập không? Tại sao điều này quan trọng khi obj có 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
  • y không được đánh giá. Vì 0 là falsy, && short-circuit và trả về 0 ngay lập tức.

Câu 3:

  • displayName: "Anonymous" (vì "" là falsy, || chuyển sang phải).
  • displayAge: 0 (vì 0 không phải null/undefined, ?? giữ lại 0).
  • Khác nhau vì || kiểm tra falsy, còn ?? chỉ kiểm tra nullish (null hoặc undefined).

Câu 4:

  • Output: null
  • obj.name không được truy cập. Vì obj là null (falsy), && short-circuit và trả về null ngay. Điều này ngăn lỗi Cannot 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 ||:

js
let theme;
if (userPreference) {
  theme = userPreference;
} else {
  theme = "light";
}
[Gợi ý]
js
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".

js
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]
js
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ùng config.timeout, mặc định 5000. 0 là giá trị hợp lệ.
  • retries: dùng config.retries, mặc định 3. 0 là giá trị hợp lệ.
  • label: dùng config.label, mặc định "default". "" không được coi là hợp lệ (dùng ||).
js
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]
js
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ệ ​

js
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:

js
const display = count ?? "N/A"; // → 0 ✅

Edge 2: Side effect không chạy ​

js
let ran = false;
"hello" || (ran = true);
console.log(ran); // false

Tạ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 ​

js
// 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):

js
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.count là 0 (falsy) → || chuyển sang "N/A".
  • data.label là "" (falsy) → || chuyển sang "N/A".
  • 0 là 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):

js
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 "0 hoặ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.theme có thể là "dark", "light", "", hoặc undefined.
  • settings.fontSize có thể là 12, 14, 16, 0, hoặc undefined.
  • settings.notifications có thể là true, false, hoặc undefined.

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. 0 nên được coi là hợp lệ (vì user muốn ẩn text?).
  • Notifications mặc định true. false nê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]
FieldOperatorLý 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.
js
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:

js
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:

  1. Tại sao code trên fail?
  2. Bạn sửa như thế nào?
  3. 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]
  1. 0 là falsy, nên || chuyển sang "—".
  2. Dùng ?? cho số liệu: data.totalUsers ?? "—".
  3. "—" chỉ nên hiển thị khi giá trị là null hoặc undefined (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)

  1. 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ào 0 và "".
  2. Đưa cho AI review: Dùng prompt: "Review my explanation of || vs ?? in JavaScript. Is it accurate? What's missing?"
  3. 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?
  4. 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ì 0 hợ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 for null or undefined. Use ?? when you want to preserve values like 0 or 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 false và NaN cũ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ới 0.
    • AI có thể không nhắc đến assignment variants (??=), vì chúng ít phổ biến hơn.
  • Kết luận: Nếu bạn chỉ ra được rằng || bỏ qua 0, "", false, NaN — còn ?? chỉ bỏ qua null/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 || 5 trả về 5, nhưng 0 ?? 5 trả 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. 0 là falsy, nên || bỏ qua 0 và trả về 5. Nhưng ?? chỉ kiểm tra null hoặc undefined. 0 không phải nullish, nên ?? giữ lại 0. Điều này quan trọng vì API thường trả về 0 là 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". 0 yế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 0 bị || từ chối không?
  • Bạn có thể dự đoán được false ?? true và false || true khác nhau không?
  • Bạn có thể giải thích tại sao NaN || 5 trả về 5 nhưng NaN ?? 5 trả về NaN khô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 mechanismExplain (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/elseImplementation (Thực hành)Code ngắn gọn, behavior giữ nguyên
Nhận diện falsy-value bugDebug (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ào 0 hoặc "" là giá trị hợp lệ.
  • [ ] Có thể refactor ít nhất 2 đoạn if/else thà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)

📴 Offline Mode — Content served from cache