Lesson 0.3.1 — Truthy & Falsy
0. Metadata
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.3 — Coercion & Equality |
| Lesson | 0.3.1 |
| Competency | C01.3 — Coercion & Equality |
| Depth | L2–L3 (Explain → Use) |
| Prerequisites | Module 0.2 hoàn thành (Values, Variables & Types, Primitive Types, Object Values, typeof) |
| Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn viết:
function showPrice(price) {
if (price) {
return `$${price}`;
}
return "Liên hệ";
}
console.log(showPrice(100)); // "$100"
console.log(showPrice(0)); // "Liên hệ" ← BUG0 là một giá hợp lệ — miễn phí. Nhưng code của bạn hiển thị "Liên hệ" vì 0 là falsy.
Bạn viết tiếp:
if (user.tags) {
renderTags(user.tags);
}user.tags là [] — array rỗng. Điều kiện vẫn đúng. [] là truthy. Nếu bạn tưởng "array rỗng = không có gì = falsy", bạn đã bị đánh lừa.
Truthy và falsy là nguồn gốc của một loại bug đặc trưng JavaScript: logic đúng với 90% data, nhưng fail với 10% edge case. Lesson này không dạy bạn "thuộc lòng" danh sách. Lesson này xây dựng khả năng dự đoán và viết check an toàn.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần:
- Biết 7 primitive types (Lesson 0.2.3).
- Phân biệt primitive và object (Lesson 0.2.1).
- Hiểu object reference và identity (Lesson 0.2.4).
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Liệt kê chính xác 8 giá trị falsy và giải thích tại sao chúng là falsy.
- Dự đoán kết quả của bất kỳ giá trị nào trong boolean context (
if,while,&&,||,!). - Phân biệt "falsy" và "empty" — ví dụ
[]falsy hay truthy?""falsy hay truthy? - Viết guard clause không bị falsy values đánh lừa.
- Nhận diện ít nhất 3 bug production phổ biến từ truthy/falsy misuse.
4. Mental Model (Mô hình tư duy)
Mental Model — Boolean Context và Closed Set
Khi JavaScript cần một boolean — ví dụ sau if, trong while, hoặc với && / || — engine không hỏi "giá trị này có nghĩa là gì?" Engine hỏi: "Giá trị này có nằm trong danh sách 8 giá trị falsy không?"
JavaScript có đúng 8 giá trị falsy. Đây là một closed set — tập đóng, cố định và không thay đổi theo phiên bản.
| Giá trị | Ý nghĩa / ghi chú |
|---|---|
false | Boolean false |
0 | Số 0 |
-0 | Âm 0 |
0n | BigInt zero |
"" | Empty string — chuỗi rỗng |
null | Giá trị null |
undefined | Giá trị chưa được xác định |
NaN | Not-a-Number |
Tất cả các giá trị khác — bao gồm "0"(type string — kí tự 0), "false", [], {}, function(){} — đều là truthy.
Code Review Lens
Khi thấy if (value) trong code review, hãy tự hỏi: "Giá trị 0, "", hoặc NaN có phải là input hợp lệ trong domain này không?" Nếu có, if (value) là bug tiềm ẩn.
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Khái niệm | Ý nghĩa |
|---|---|
| Boolean context | Ngữ cảnh mà JavaScript bắt buộc cần boolean: if, while, for, &&, ` |
| Falsy | 8 giá trị được coerce thành false. |
| Truthy | Mọi giá trị không nằm trong danh sách 8 falsy. |
| Coerce | Chuyển đổi ngầm (implicit) từ một type sang boolean. |
Supporting (Hỗ trợ)
!operator buộc boolean coercion trước khi phủ định.!!valuelà pattern để explicit coerce sang boolean (nhưng cẩn thận với các edge case).
Awareness (Biết tồn tại)
document.alllà một legacy quirk đặc biệt (falsy dù là object). Chỉ cần biết nó tồn tại trong lịch sử, không cần nhớ.Boolean(value)là hàm constructor để explicit coerce. Sẽ được đào sâu ở Lesson 0.3.3.
Out of Scope (Không học trong bài này)
==coercion rules: Thuộc Lesson 0.3.2.- Explicit conversion:
Number(),String(),Boolean(). Thuộc Lesson 0.3.3. ??(nullish coalescing): Phân biệtnull/undefinedvới các falsy khác. Thuộc Lesson 0.4.3.
6. Worked Example (Ví dụ phân tích từng bước)
Xét đoạn code sau:
const config = {
timeout: 0,
label: "",
tags: [],
retries: NaN,
name: undefined
};
if (config.timeout) {
console.log("Has timeout");
}
if (config.label) {
console.log("Has label");
}
if (config.tags) {
console.log("Has tags");
}
if (config.retries) {
console.log("Has retries");
}
if (config.name) {
console.log("Has name");
}Step 1 — config.timeout
config.timeout === 00 nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.
Step 2 — config.label
config.label === """" (empty string) nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.
Step 3 — config.tags
config.tags === [][] là object. Không nằm trong danh sách 8 falsy. Coerce thành true. Vào if.
Step 4 — config.retries
config.retries === NaNNaN nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.
Step 5 — config.name
config.name === undefinedundefined nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.
Output
Has tagsWARNING
Developer thường tưởng [] (rỗng) là "không có gì" nên falsy. Sai. [] là object, object luôn truthy. Chỉ có "" là falsy trong các giá trị "trống".
7. Prediction Exercise (Bài tập dự đoán)
Đừng chạy code. Dự đoán kết quả (true hay false — nghĩa là vào if hay không) và giải thích bằng danh sách 8 falsy.
P1
if ("0") { console.log("yes"); } else { console.log("no"); }P2
if ([]) { console.log("yes"); } else { console.log("no"); }P3
if ({}) { console.log("yes"); } else { console.log("no"); }P4
if (0) { console.log("yes"); } else { console.log("no"); }P5
if (NaN) { console.log("yes"); } else { console.log("no"); }P6
if ("false") { console.log("yes"); } else { console.log("no"); }P7
if (-0) { console.log("yes"); } else { console.log("no"); }P8
if (0n) { console.log("yes"); } else { console.log("no"); }P9
if (false) { console.log("yes"); } else { console.log("no"); }[Đáp án & Giải thích]
| # | Output | Lý do |
|---|---|---|
| P1 | yes | "0" là string không rỗng. Không nằm trong 8 falsy. |
| P2 | yes | [] là object. Object luôn truthy. |
| P3 | yes | {} là object. Object luôn truthy. |
| P4 | no | 0 nằm trong 8 falsy. |
| P5 | no | NaN nằm trong 8 falsy. |
| P6 | yes | "false" là string không rỗng. Nội dung là chữ "false" nhưng vẫn là string hợp lệ, truthy. |
| P7 | no | -0 nằm trong 8 falsy (cùng nhóm với 0). |
| P8 | no | 0n (BigInt zero) nằm trong 8 falsy. |
| P9 | no | false nằm trong 8 falsy. |
8. Implementation Lab (Bài lab thực hành)
Lab A — Guided (Có hướng dẫn)
Sửa đoạn code sau để xử lý đúng cả trường hợp timeout = 0 (hợp lệ) và timeout = undefined (chưa set):
function getTimeout(config) {
if (config.timeout) {
return config.timeout;
}
return 5000;
}
console.log(getTimeout({ timeout: 3000 })); // 3000
console.log(getTimeout({ timeout: 0 })); // 5000 ← BUG: 0 là hợp lệ
console.log(getTimeout({})); // 5000[Đáp án tham khảo]
function getTimeout(config) {
if (config.timeout !== undefined) {
return config.timeout;
}
return 5000;
}
console.log(getTimeout({ timeout: 3000 })); // 3000
console.log(getTimeout({ timeout: 0 })); // 0
console.log(getTimeout({})); // 5000Lab B — Partial Scaffold (Khung sườn)
Hoàn thành hàm hasValue kiểm tra một field có "có dữ liệu" hay không theo định nghĩa:
nullvàundefined→false(không có dữ liệu).""(empty string) →false(xem như không có dữ liệu).0vàNaN→true(0 là số hợp lệ, NaN tuy không hợp lệ nhưng vẫn là "có giá trị" trong lab này).[]→true(array dù rỗng vẫn là object có giá trị).- Các giá trị khác →
true.
function hasValue(field) {
// TODO: check null và undefined
// TODO: check empty string
// TODO: các trường hợp khác return true
// implement here
}[Đáp án tham khảo]
function hasValue(field) {
if (field === null || field === undefined) return false;
if (field === "") return false;
return true;
}Lab C — Independent (Tự làm)
Viết hàm formatUser nhận một user object và trả về string hiển thị. Yêu cầu:
name: hiển thị nếu là string không rỗng. Nếu rỗng hoặc thiếu →"Anonymous".age: hiển thị nếu là number (kể cả0). Nếu thiếu → không hiển thị age.bio: hiển thị nếu là string. Nếu rỗng → không hiển thị bio.
formatUser({ name: "A", age: 25, bio: "Dev" }); // "A, 25, Dev"
formatUser({ name: "", age: 0, bio: "" }); // "Anonymous, 0"
formatUser({ name: "B" }); // "B"[Đáp án tham khảo]
function formatUser(user) {
const name = user.name !== undefined && user.name !== "" ? user.name : "Anonymous";
const agePart = typeof user.age === "number" ? `, ${user.age}` : "";
const bioPart = typeof user.bio === "string" && user.bio !== "" ? `, ${user.bio}` : "";
return `${name}${agePart}${bioPart}`;
}9. Edge Cases (Các trường hợp ngoại lệ)
"" vs " " vs "\n"
Boolean(""); // false
Boolean(" "); // true
Boolean("\n"); // trueChỉ có empty string (length === 0) là falsy. Bất kỳ ký tự nào — kể cả space, tab, newline — đều là truthy.
0 vs "0"
if (0) // false
if ("0") // true
if ("0" == 0) // true (coercion, nhưng thuộc Lesson 0.3.2)Đây là lý do tại sao form input (luôn là string) dễ gây confusion. Input "0" từ <input type="text"> là truthy, nhưng nếu bạn parse thành number thì thành 0 (falsy).
[] và {} luôn truthy
if ([]) // true
if ({}) // true
if ([] == false) // true (coercion, Lesson 0.3.2)[] == false là true vì == có coercion. Nhưng [] trong boolean context (if) là truthy. Đừng nhầm lẫn hai ngữ cảnh.
NaN là falsy nhưng không phải "thiếu dữ liệu"
if (NaN) // falseNaN là falsy, nhưng nó thường báo hiệu bug tính toán chứ không phải "chưa có giá trị". Đừng dùng if (value) để bắt NaN — bạn sẽ không biết đó là NaN, 0, hay "".
10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): Form đăng ký hiển thị "Vui lòng nhập tuổi" ngay cả khi user đã nhập 0.
Reproduction (Tái hiện lỗi):
function validateAge(age) {
if (!age) {
return "Vui lòng nhập tuổi";
}
if (age < 0 || age > 150) {
return "Tuổi không hợp lệ";
}
return "Hợp lệ";
}
console.log(validateAge(25)); // "Hợp lệ"
console.log(validateAge(0)); // "Vui lòng nhập tuổi" ← BUG
console.log(validateAge("")); // "Vui lòng nhập tuổi"
console.log(validateAge(null)); // "Vui lòng nhập tuổi"Evidence (Bằng chứng):
0là tuổi hợp lệ (trẻ sơ sinh) nhưng bị từ chối.!agecoi0,"",null,undefined,NaNnhư nhau.
Hypothesis (Giả thuyết): Developer dùng !age để check "chưa nhập". Nhưng ! coerces 0 thành true, nên 0 bị coi là "chưa nhập".
Verification (Xác minh):
console.log(!0); // true
console.log(!""); // true
console.log(!null); // true
console.log(!undefined); // trueCả 4 giá trị khác nhau về semantic nhưng đều bị ! xử lý giống nhau.
Root Cause (Nguyên nhân gốc rễ): Dùng truthy/falsy check cho một domain mà 0 là giá trị hợp lệ. !age không phân biệt "thiếu dữ liệu" với "dữ liệu là 0".
Fix (Sửa):
function validateAge(age) {
if (age === undefined || age === null || age === "") {
return "Vui lòng nhập tuổi";
}
if (age < 0 || age > 150) {
return "Tuổi không hợp lệ";
}
return "Hợp lệ";
}Hoặc nếu cần check cả NaN:
function validateAge(age) {
if (age === undefined || age === null || age === "" || Number.isNaN(age)) {
return "Vui lòng nhập tuổi";
}
if (age < 0 || age > 150) {
return "Tuổi không hợp lệ";
}
return "Hợp lệ";
}Prevention (Phòng ngừa):
- Khi domain cho phép
0,"", hoặcfalselà giá trị hợp lệ, không bao giờ dùngif (value)hoặcif (!value)để check "thiếu dữ liệu". - Dùng explicit check:
=== undefined,=== null,=== "", hoặcNumber.isNaN().
11. Design Exercise (Bài tập thiết kế giải pháp)
Context
Bạn đang review code của một form có 4 field: name (string), quantity (number), tags (array), settings (object). Đồng nghiệp viết hàm kiểm tra "đã điền đủ chưa" như sau:
function isComplete(form) {
return form.name && form.quantity && form.tags && form.settings;
}Câu hỏi:
- Hàm này fail với những giá trị hợp lệ nào?
- Viết lại
isCompleteđể đúng với yêu cầu: field được coi là "đã điền" nếu nó không phảinullhoặcundefined.
[Đáp án tham khảo]
Hàm fail với:
name: ""(empty string là falsy, nhưng có thể là input hợp lệ).quantity: 0(0 là falsy, nhưng 0 là quantity hợp lệ).tags: [](array rỗng là truthy, nên OK, nhưng nếu logic yêu cầu "phải có ít nhất 1 tag" thì lại sai).settings: {}(object rỗng là truthy, nên OK).
Fix:
function isComplete(form) {
return (
form.name !== undefined && form.name !== null &&
form.quantity !== undefined && form.quantity !== null &&
form.tags !== undefined && form.tags !== null &&
form.settings !== undefined && form.settings !== null
);
}Hoặc ngắn gọn hơn nếu bạn đã biết ?? (sẽ học ở Lesson 0.4.3):
function isComplete(form) {
return [form.name, form.quantity, form.tags, form.settings]
.every(v => v !== undefined && v !== null);
}12. Production Scenario (Tình huống thực tế)
Backend API trả về:
{
"count": 0,
"label": "",
"items": [],
"metadata": null,
"score": NaN
}Frontend render:
function renderStats(data) {
return {
count: data.count || "N/A",
label: data.label || "Untitled",
items: data.items || "No items",
metadata: data.metadata || "No metadata",
score: data.score || "Not scored"
};
}Câu hỏi:
- Trường hợp nào trả về kết quả sai so với dữ liệu gốc?
- Tại sao
||(logical OR) không phù hợp ở đây? - Sửa như thế nào để chỉ thay thế khi giá trị thực sự thiếu (
null/undefined)?
[Đáp án tham khảo]
Các trường hợp sai:
count: 0→"N/A"(0 là falsy, bị||thay thế).label: ""→"Untitled"(empty string falsy, bị thay thế).score: NaN→"Not scored"(NaN falsy, bị thay thế).
||trả về operand đầu tiên truthy. Nó không phân biệt"thiếu dữ liệu"với"dữ liệu là 0 hay NaN".Fix dùng explicit check:
function renderStats(data) {
return {
count: data.count !== undefined && data.count !== null ? data.count : "N/A",
label: data.label !== undefined && data.label !== null ? data.label : "Untitled",
items: data.items !== undefined && data.items !== null ? data.items : "No items",
metadata: data.metadata !== undefined && data.metadata !== null ? data.metadata : "No metadata",
score: data.score !== undefined && data.score !== null ? data.score : "Not scored"
};
}Hoặc dùng helper:
function fallback(value, defaultValue) {
return value !== undefined && value !== null ? value : defaultValue;
}13. AI-assisted Exercise (Bài tập với AI)
Level B — Challenge (Thách thức AI)
- Tự trả lời trước: Viết ra giấy 8 giá trị falsy. Đánh dấu xem bạn có tự tin 100% với từng giá trị không.
- Hỏi AI: "List all falsy values in JavaScript."
- So sánh: AI có liệt kê đủ 8 giá trị không? AI có nhắc đến
-0và0nkhông? Hay AI chỉ liệt kê 6 giá trị phổ biến? - Verify: Kiểm tra lại bằng MDN page "Falsy". Xem documentation có nhắc đến
document.allkhông?
Gợi ý
Hãy để ý xem AI có thêm document.all vào danh sách không. Một số AI cũ sẽ thêm document.all vì nó từng là falsy trong lịch sử, nhưng điều này gây confusion. Cũng để ý xem AI có giải thích tại sao chỉ có 8 giá trị (closed set) hay chỉ liệt kê như một danh sách cần nhớ.
Đáp án tham khảo
Bạn nghĩ
- 8 giá trị falsy:
false,0,-0,0n,"",null,undefined,NaN. - Đây là closed set — không có giá trị nào khác.
- 8 giá trị falsy:
AI trả lời
- AI thường liệt kê:
false,0,"",null,undefined,NaN. - AI thường bỏ sót
-0và0n. - AI có thể thêm
document.allnhư một "legacy falsy value". - AI hiếm khi nhấn mạnh đây là closed set — thay vào đó trình bày như một danh sách cần thuộc.
- AI thường liệt kê:
So sánh
- AI đúng về 6 giá trị phổ biến.
- AI thiếu:
-0và0nlà falsy theo spec. Việc bỏ sót này có thể gây bug nếu bạn làm việc với BigInt hoặc signed zero. - AI thừa/ confusing:
document.alllà host object quirk, không phải language-level falsy value.
Điểm AI nói sai hoặc quá mơ hồ
- AI thường nói "These are the values that evaluate to false in boolean context" mà không giải thích cơ chế (spec-defined closed set).
- AI có thể gợi ý dùng
if (value)để check "empty" — đây là lời khuyên nguy hiểm nếu không có context.
Kết luận
- Nếu bạn liệt kê đủ 8 giá trị và giải thích đây là closed set, bạn đã hiểu chính xác hơn hầu hết các câu trả lời AI.
- Concept này sẽ quay lại ở Lesson 0.3.2 (Equality coercion), Lesson 0.4.3 (
??nullish coalescing), và Stage 8 (React conditional rendering).
14. Teach Back (Dạy lại)
Giải thích cho một đồng nghiệp junior trong 2 phút, không dùng từ "nó thế":
"Tại sao
[](array rỗng) là truthy nhưng""(string rỗng) lại là falsy? Cả hai đều 'rỗng' mà?"
Yêu cầu:
- Dùng đúng từ: closed set, 8 giá trị falsy, object, primitive.
- Không được nói "vì JavaScript quy định vậy".
Mô phỏng
- Bạn nói
- "Không phải JavaScript nhìn vào nội dung và nói 'cái này rỗng nên false'. JavaScript có một danh sách đóng gồm đúng 8 giá trị được coi là falsy:
false,0,-0,0n,"",null,undefined, vàNaN." - "Nếu giá trị của bạn không nằm trong danh sách 8 đó, dù nó trông như thế nào, nó cũng là truthy."
- "
[]là một object. Object không nằm trong danh sách 8 falsy. Vì vậy[]là truthy. Còn""là string rỗng, và nó nằm trong danh sách 8. Vì vậy""là falsy." - "Điều này có nghĩa là bạn không thể dùng trực giác 'rỗng = false'. Bạn phải kiểm tra danh sách 8."
- "Không phải JavaScript nhìn vào nội dung và nói 'cái này rỗng nên false'. JavaScript có một danh sách đóng gồm đúng 8 giá trị được coi là falsy:
💡 Liên tưởng: Danh sách 8 falsy giống như danh sách đen (blacklist) của một club. Chỉ có 8 người trong danh sách bị cấm. Tất cả những người khác — dù trông như thế nào — đều được vào.
[]là một vị khách (object) không nằm trong danh sách 8, nên được vào.""là một vị khách nằm trong danh sách 8, nên bị từ chối.
Gợi ý đánh giá bản thân
- Đồng nghiệp có hỏi "vậy
{}cũng truthy à?" không? Nếu có, bạn đã truyền đạt đúng mental model (object không nằm trong 8 falsy). - Bạn có thể liệt kê đủ 8 giá trị từ đầu đến cuối không mà không nhìn tài liệu?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá |
|---|---|
| Liệt kê 8 giá trị falsy | Recall (Nhớ lại) |
| Dự đoán truthy/falsy của value bất kỳ | Prediction (Dự đoán) |
| Viết guard clause an toàn | Implementation (Thực hành) |
Debug bug từ if (value) | Debug Lab (Gỡ lỗi) |
Phân biệt "" và [] trong boolean context | Explanation (Giải thích) |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể liệt kê đúng 8 giá trị falsy từ đầu đến cuối.
- [ ] Có thể dự đoán đúng 8/8 scenarios trong Prediction Exercise.
- [ ] Có thể viết hàm kiểm tra "thiếu dữ liệu" mà không nhầm
0,"", hoặcfalsevớinull/undefined. - [ ] Có thể debug trường hợp
if (count)hoặcif (label)gây lỗi với0hoặc"". - [ ] Không còn dùng
if (value)để check "có dữ liệu" trong domain cho phép0,"", hoặcfalse.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Module 0.2 — Values, Variables & Types. Bạn đã biết primitive vs object,
typeof, object identity.
Current (Hiện tại): Lesson 0.3.1 — Truthy & Falsy. Bạn hiểu boolean coercion, danh sách 8 falsy closed set, và cách object luôn truthy.
Next (Tiếp theo):
- Lesson 0.3.2 — Equality (
==vs===, coercion trong comparison).- Lesson 0.3.3 — Type Conversion (explicit và implicit conversion giữa các type).
- Lesson 0.4.3 — Short-circuit Evaluation (
&&,||,??và sự khác biệt giữa falsy và nullish).- Stage 8 — React: Conditional rendering (
{count && <Component />}vớicount = 0là classic React bug từ truthy/falsy).- Stage 9 — Production Frontend: Form validation và input handling với các giá trị edge case.