Skip to content

Lesson 0.3.1 — Truthy & Falsy ​

Bài 0.3.1 — Truthy & Falsy
8 giá trị falsy và cách JavaScript đánh giá điều kiện • 25 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.3 — Coercion & Equality
Lesson0.3.1
CompetencyC01.3 — Coercion & Equality
DepthL2–L3 (Explain → Use)
PrerequisitesModule 0.2 hoàn thành (Values, Variables & Types, Primitive Types, Object Values, typeof)
Cognitive LoadMedium

1. Why This Exists (Vì sao cần học) ​

Bạn viết:

js
function showPrice(price) {
  if (price) {
    return `$${price}`;
  }
  return "Liên hệ";
}

console.log(showPrice(100)); // "$100"
console.log(showPrice(0));   // "Liên hệ"  ← BUG

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

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

  1. Liệt kê chính xác 8 giá trị falsy và giải thích tại sao chúng là falsy.
  2. Dự đoán kết quả của bất kỳ giá trị nào trong boolean context (if, while, &&, ||, !).
  3. Phân biệt "falsy" và "empty" — ví dụ [] falsy hay truthy? "" falsy hay truthy?
  4. Viết guard clause không bị falsy values đánh lừa.
  5. 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?"

Boolean context của JavaScript kiểm tra value có thuộc closed set gồm 8 giá trị falsy hay không; thuộc tập thì cho false, ngoài tập thì cho true.

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ú
falseBoolean false
0Số 0
-0Âm 0
0nBigInt zero
""Empty string — chuỗi rỗng
nullGiá trị null
undefinedGiá trị chưa được xác định
NaNNot-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 contextNgữ cảnh mà JavaScript bắt buộc cần boolean: if, while, for, &&, `
Falsy8 giá trị được coerce thành false.
TruthyMọi giá trị không nằm trong danh sách 8 falsy.
CoerceChuyển đổi ngầm (implicit) từ một type sang boolean.

Supporting (Hỗ trợ) ​

  • ! operator buộc boolean coercion trước khi phủ định.
  • !!value là 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.all là 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ệt null/undefined vớ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:

js
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

js
config.timeout === 0

0 nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.

Step 2 — config.label

js
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

js
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

js
config.retries === NaN

NaN nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.

Step 5 — config.name

js
config.name === undefined

undefined nằm trong danh sách 8 falsy. Coerce thành false. Không vào if.

Output

text
Has tags

WARNING

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

js
if ("0") { console.log("yes"); } else { console.log("no"); }

P2

js
if ([]) { console.log("yes"); } else { console.log("no"); }

P3

js
if ({}) { console.log("yes"); } else { console.log("no"); }

P4

js
if (0) { console.log("yes"); } else { console.log("no"); }

P5

js
if (NaN) { console.log("yes"); } else { console.log("no"); }

P6

js
if ("false") { console.log("yes"); } else { console.log("no"); }

P7

js
if (-0) { console.log("yes"); } else { console.log("no"); }

P8

js
if (0n) { console.log("yes"); } else { console.log("no"); }

P9

js
if (false) { console.log("yes"); } else { console.log("no"); }
[Đáp án & Giải thích]
#OutputLý do
P1yes"0" là string không rỗng. Không nằm trong 8 falsy.
P2yes[] là object. Object luôn truthy.
P3yes{} là object. Object luôn truthy.
P4no0 nằm trong 8 falsy.
P5noNaN nằm trong 8 falsy.
P6yes"false" là string không rỗng. Nội dung là chữ "false" nhưng vẫn là string hợp lệ, truthy.
P7no-0 nằm trong 8 falsy (cùng nhóm với 0).
P8no0n (BigInt zero) nằm trong 8 falsy.
P9nofalse 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):

js
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]
js
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({}));                // 5000

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

  • null và undefined → false (không có dữ liệu).
  • "" (empty string) → false (xem như không có dữ liệu).
  • 0 và 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.
js
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]
js
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.
js
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]
js
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"

js
Boolean("");      // false
Boolean(" ");     // true
Boolean("\n");    // true

Chỉ 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"

js
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

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

js
if (NaN) // false

NaN 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):

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

  • 0 là tuổi hợp lệ (trẻ sơ sinh) nhưng bị từ chối.
  • !age coi 0, "", null, undefined, NaN như 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):

js
console.log(!0);        // true
console.log(!"");       // true
console.log(!null);     // true
console.log(!undefined); // true

Cả 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):

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

js
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ặc false là giá trị hợp lệ, không bao giờ dùng if (value) hoặc if (!value) để check "thiếu dữ liệu".
  • Dùng explicit check: === undefined, === null, === "", hoặc Number.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:

js
function isComplete(form) {
  return form.name && form.quantity && form.tags && form.settings;
}

Câu hỏi:

  1. Hàm này fail với những giá trị hợp lệ nào?
  2. Viết lại isComplete để đúng với yêu cầu: field được coi là "đã điền" nếu nó không phải null hoặc undefined.
[Đáp án tham khảo]
  1. 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).
  2. Fix:

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

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

json
{
  "count": 0,
  "label": "",
  "items": [],
  "metadata": null,
  "score": NaN
}

Frontend render:

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

  1. Trường hợp nào trả về kết quả sai so với dữ liệu gốc?
  2. Tại sao || (logical OR) không phù hợp ở đây?
  3. Sửa như thế nào để chỉ thay thế khi giá trị thực sự thiếu (null/undefined)?
[Đáp án tham khảo]
  1. 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ế).
  2. || 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".

  3. Fix dùng explicit check:

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

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

  1. 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.
  2. Hỏi AI: "List all falsy values in JavaScript."
  3. So sánh: AI có liệt kê đủ 8 giá trị không? AI có nhắc đến -0 và 0n không? Hay AI chỉ liệt kê 6 giá trị phổ biến?
  4. Verify: Kiểm tra lại bằng MDN page "Falsy". Xem documentation có nhắc đến document.all khô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.
  • AI trả lời

    • AI thường liệt kê: false, 0, "", null, undefined, NaN.
    • AI thường bỏ sót -0 và 0n.
    • AI có thể thêm document.all như 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.
  • So sánh

    • AI đúng về 6 giá trị phổ biến.
    • AI thiếu: -0 và 0n là 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.all là 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."

💡 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ị falsyRecall (Nhớ lại)
Dự đoán truthy/falsy của value bất kỳPrediction (Dự đoán)
Viết guard clause an toànImplementation (Thực hành)
Debug bug từ if (value)Debug Lab (Gỡ lỗi)
Phân biệt "" và [] trong boolean contextExplanation (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ặc false với null/undefined.
  • [ ] Có thể debug trường hợp if (count) hoặc if (label) gây lỗi với 0 hoặc "".
  • [ ] Không còn dùng if (value) để check "có dữ liệu" trong domain cho phép 0, "", hoặc false.

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ới count = 0 là classic React bug từ truthy/falsy).
  • Stage 9 — Production Frontend: Form validation và input handling với các giá trị edge case.
📴 Offline Mode — Content served from cache