Skip to content

Lesson 0.4.1 — Operators ​

Bài 0.4.1 — Operators
Arithmetic, Comparison, Logical & Assignment • 24 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.4 — Operators & Control Flow
Lesson0.4.1
CompetencyC01.4 — Control Flow (foundation)
DepthL2–L3 (Explain → Use)
PrerequisitesModule 0.3 — Coercion & Equality (đặc biệt == vs ===, truthy/falsy, + string context)
Cognitive LoadMedium

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

Bạn viết:

js
const result = user && user.name || "Anonymous";

user tồn tại, user.name là "" (empty string). Kết quả: "Anonymous".

Bạn ngạc nhiên: "Tại sao? user đúng, user.name đúng (một string), sao lại ra Anonymous?"

Bạn không biết rằng && và || không trả về boolean. Chúng trả về operand — giá trị quyết định kết quả. "" là falsy, nên || nhảy sang "Anonymous".

Hoặc bạn viết:

js
const total = price + tax;

price là 100, tax là "10". Kết quả: "10010". Bạn đã học coercion, nhưng nếu không nắm rõ toán tử nào trigger context nào, bạn vẫn sẽ bị bất ngờ.

Lesson này xây dựng mental model về operators như các hàm built-in trả về value — không phải chỉ là "ký hiệu toán học".

2. Prerequisites (Yêu cầu đầu vào) ​

Trước khi học bài này, bạn cần:

  • Hiểu coercion và == vs === (Module 0.3).
  • Biết 8 giá trị falsy (Lesson 0.3.1).
  • Phân biệt primitive types (Lesson 0.2.3).

3. Learning Objectives (Mục tiêu học tập) ​

Sau bài này, bạn có thể:

  1. Phân loại operators thành 4 nhóm: Arithmetic, Comparison, Logical, Assignment.
  2. Giải thích tại sao && và || trả về operand, không phải boolean.
  3. Dự đoán kết quả của &&, ||, và ! với các giá trị non-boolean.
  4. Sử dụng assignment operators (=, +=, -=, *=) và hiểu chúng trả về giá trị.
  5. Nhận diện bug từ việc nhầm lẫn &&/|| trả về boolean.

4. Mental Model (Mô hình tư duy) ​

Mental Model — Operators là Hàm Trả về Value

Đừng nghĩ operators chỉ là "phép tính". Mỗi operator là một hàm nhận 1 hoặc 2 operand và trả về một value duy nhất.

text
operand ──┐
          ├──→ [Operator] ──→ result value
operand ──┘

Điều quan trọng nhất cần nhớ:

&& và || không trả về true/false. Chúng trả về chính operand quyết định kết quả.

text
a && b
  │
  ├── a falsy? → return a (không cần xem b)
  │
  └── a truthy? → return b

a || b
  │
  ├── a truthy? → return a (không cần xem b)
  │
  └── a falsy? → return b

Code Review Lens

Khi thấy const x = a || b;, đừng đọc là "x là true hoặc false". Hãy đọc là "x nhận giá trị a nếu a truthy, ngược lại nhận b". Điều này thay đổi cách bạn review code hoàn toàn.

5. Core Concepts (Các khái niệm cốt lõi) ​

Essential (Bắt buộc) ​

NhómOperatorsÝ nghĩa then chốt
Arithmetic+ - * / % **+ có dual behavior (string concat vs numeric add). % là remainder, không phải modulo toán học. ** là exponentiation.
Comparison< > <= >= == ====== không coercion (đã học Module 0.3). </> trigger numeric context.
Logical! && ||! trả về boolean. && và || trả về operand.
Assignment= += -= *== trả về giá trị đã gán. a += b tương đương a = a + b.

Supporting (Hỗ trợ) ​

  • ?? (nullish coalescing): trả về right operand nếu left là null hoặc undefined. Sẽ đào sâu ở Lesson 0.4.3.
  • &&=, ||=, ??= (logical assignment): gán nếu điều kiện đúng. Ví dụ a ??= b tương đương a = a ?? b.

Awareness (Biết tồn tại) ​

  • Unary operators: +x (convert to number), -x (negate), typeof x (đã học 0.2.5).
  • Bitwise operators (&, |, ^, ~, <<, >>, >>>): tồn tại nhưng không thuộc core curriculum Stage 0.

Out of Scope (Không học trong bài này) ​

  • Short-circuit evaluation patterns: user?.profile, value ?? fallback. Thuộc Lesson 0.4.3.
  • Ternary operator ? :: Thuộc Lesson 0.4.2 (Conditional Logic).
  • Bitwise operators: Không cần cho frontend core.
  • in và instanceof: Thuộc Stage 2 (Object Model).

6. Worked Example (Ví dụ phân tích từng bước) ​

Xét đoạn code sau:

js
const price = 100;
const tax = "10";
const total = price + tax;

const min = 0;
const max = 200;
const inRange = price >= min && price <= max;

const fallback = 0 || "default";
const combined = "hello" && 42;

let base = 10;
const added = (base += 5);

Step 1 — Arithmetic +

js
const total = price + tax;

price là number, tax là string. + thấy string bên phải → string context. 100 chuyển thành "100", nối với "10". Kết quả: "10010".

Step 2 — Comparison >=

js
price >= min

100 >= 0 → numeric comparison. Cả hai là number. Kết quả: true.

Step 3 — Logical &&

js
price >= min && price <= max

&& đánh giá trái trước. Trái là true (truthy). && trả về right operand. Đánh giá price <= max → 100 <= 200 → true. Kết quả: true.

Step 4 — Logical ||

js
const fallback = 0 || "default";

|| đánh giá trái trước. 0 là falsy. || trả về right operand. Kết quả: "default".

Step 5 — Logical && với non-boolean

js
const combined = "hello" && 42;

&& đánh giá trái. "hello" là truthy. && trả về right operand — không phải true, mà là 42. Kết quả: 42.

Step 6 — Assignment +=

js
const added = (base += 5);

base += 5 tương đương base = base + 5. base từ 10 thành 15. Biểu thức assignment trả về giá trị đã gán: 15. Kết quả: added là 15.

7. Prediction Exercise (Bài tập dự đoán) ​

Đừng chạy code. Dự đoán output và giải thích bằng mental model "operator trả về value".

P1

js
console.log(2 ** 3);
console.log(10 / "2");

P2

js
console.log("10" > 2);
console.log("a" > "b");

P3

js
console.log(0 || 100);
console.log("" && "hidden");
console.log(null || undefined);

P4

js
console.log(!"hello");
console.log(!0);

P5

js
let a = 5;
console.log(a *= 2);
console.log(a);

P6

js
const user = { name: "" };
const display = user && user.name || "Guest";
console.log(display);
[Đáp án & Giải thích]

P1

  • 2 ** 3 → 8 (2 mũ 3).
  • 10 / "2" → 5 (numeric context, "2" → 2).

P2

  • "10" > 2 → true (string "10" → number 10, 10 > 2).
  • "a" > "b" → false (string comparison theo Unicode code point: a = 97, b = 98, 97 > 98 là false)

Lưu ý : Đây chỉ là simplification — JavaScript thực tế so sánh theo UTF-16 code unit sequence. string comparison theo UTF-16 code unit (với ASCII: a = 0x0061, b = 0x0062, 0x0061 > 0x0062 là false)

P3

  • 0 || 100 → 100 (0 falsy, || trả right operand).
  • "" && "hidden" → "" ("" falsy, && trả left operand ngay, không xem "hidden").
  • null || undefined → undefined (null falsy, || trả right operand).

P4

  • !"hello" → false (non-empty string truthy, ! phủ định → false).
  • !0 → true (0 falsy, ! phủ định → true).

P5

  • a *= 2 → 10 (assignment trả về giá trị mới: 5 * 2 = 10).
  • a → 10 (biến đã được update).

P6

  • user truthy → && xem tiếp user.name → "" (falsy).
  • "" || "Guest" → "Guest" ("" falsy, || trả right).
  • Kết quả: "Guest".

8. Implementation Lab (Bài lab thực hành) ​

Lab A — Guided (Có hướng dẫn) ​

Sửa đoạn code để total là number đúng (không phải string), và label có fallback hợp lý:

js
const price = 100;
const taxStr = "10";

// TODO: total phải là number 110
const total = price + taxStr;

const name = "";
// TODO: nếu name rỗng, dùng "Unknown" — nhưng 0 là tên hợp lệ nếu name là number
const label = name || "Unknown";
[Đáp án tham khảo]
js
const price = 100;
const taxStr = "10";

const total = price + Number(taxStr); // 110

const name = "";
// Chỉ check empty string, không dùng || vì || bắt cả 0
const label = name === "" ? "Unknown" : name;

Lab B — Partial Scaffold (Khung sườn) ​

Hoàn thành hàm clamp giới hạn một số trong khoảng [min, max]:

js
function clamp(value, min, max) {
  // TODO: nếu value < min, trả về min
  // TODO: nếu value > max, trả về max
  // TODO: nếu không, trả về value
  // Chỉ dùng comparison operators và ternary (hoặc logical nếu bạn muốn thử)
  // implement here
}
[Đáp án tham khảo]
js
function clamp(value, min, max) {
  return value < min ? min 
         : value > max ? max 
         : value;
}

// Hoặc dùng logical (nhưng kém readable):
// return value < min && min || value > max && max || value;

Lab C — Independent (Tự làm) ​

Viết hàm getDisplayName nhận user object. Yêu cầu:

  • Nếu user là null/undefined, trả về "Anonymous".
  • Nếu user.name là string không rỗng, trả về user.name.
  • Nếu user.name là string rỗng, trả về "User" (không phải "Anonymous").
  • Không dùng if, chỉ dùng logical/comparison operators và ternary.
js
console.log(getDisplayName({ name: "A" }));  // "A"
console.log(getDisplayName({ name: "" }));   // "User"
console.log(getDisplayName(null));           // "Anonymous"
[Đáp án tham khảo]
js
function getDisplayName(user) {
  return user == null
    ? "Anonymous"
    : user.name !== ""
      ? user.name || "Anonymous"
      : "User";
}

// Hoặc rõ ràng hơn:
function getDisplayName(user) {
  if (user == null) return "Anonymous";
  if (user.name === "") return "User";
  return user.name || "Anonymous";
}

9. Edge Cases (Các trường hợp ngoại lệ) ​

&& và || trả về operand, không phải boolean

js
const x = "hello" && 42;  // x = 42, không phải true
const y = 0 || "fallback"; // y = "fallback", không phải true

Đây là feature, không phải bug. Nhưng nếu bạn tưởng chúng trả về boolean, bạn sẽ viết code sai.

% là remainder, không phải modulo toán học

js
console.log(10 % 3);   // 1
console.log(-10 % 3);  // -1

Với số âm, kết quả giữ dấu của dividend. Khác với modulo toán học luôn dương.

+ với object

js
console.log([] + 1);      // "1"
console.log({} + 1);      // "[object Object]1"

+ trigger string context khi một bên là object (sau khi object → primitive). Thuộc coercion (Module 0.3) nhưng cần nhắc lại ở đây.

Assignment trả về value

js
let a, b;
console.log(a = b = 1); // 1
console.log(a, b);      // 1, 1

b = 1 trả về 1, rồi a = 1. Điều này cho phép chaining nhưng cũng gây confusion.

10. Debug Lab (Bài lab gỡ lỗi) ​

Symptom (Triệu chứng): Hàm setConfig bỏ qua giá trị 0 của user và dùng default.

Reproduction (Tái hiện lỗi):

js
function setConfig(options) {
  const timeout = options.timeout || 5000;
  const retries = options.retries || 3;
  return { timeout, retries };
}

console.log(setConfig({ timeout: 0, retries: 0 }));
// { timeout: 5000, retries: 3 }  ← BUG: 0 là giá trị hợp lệ

Evidence (Bằng chứng):

  • options.timeout là 0 (falsy). || thấy falsy, trả về right operand 5000.
  • options.retries là 0 (falsy). Tương tự.

Hypothesis (Giả thuyết): Developer dùng || để set default, nhưng || trả về first truthy operand. 0 là falsy nên bị skip.

Verification (Xác minh):

js
console.log(0 || 5000); // 5000
console.log(2 || 5000); // 2

Root Cause (Nguyên nhân gốc rễ):|| không phân biệt "thiếu dữ liệu" (null/undefined) với "dữ liệu là 0". Nó chỉ quan tâm truthy/falsy.

Fix (Sửa):

js
function setConfig(options) {
  const timeout = options.timeout !== undefined ? options.timeout : 5000;
  const retries = options.retries !== undefined ? options.retries : 3;
  return { timeout, retries };
}

Hoặc dùng ?? (sẽ học ở Lesson 0.4.3):

js
const timeout = options.timeout ?? 5000;

Prevention (Phòng ngừa):

  • Không dùng || để set default cho giá trị số khi 0 là hợp lệ.
  • Dùng explicit check !== undefined hoặc ?? (nullish coalescing).

11. Design Exercise (Bài tập thiết kế giải pháp) ​

Context

Bạn đang khởi tạo một object config. Mỗi property có thể đã tồn tại (do user truyền vào) hoặc chưa. Bạn muốn gán default chỉ khi property chưa tồn tại (tức là undefined).

So sánh 3 cách:

js
// Cách A
config.timeout = config.timeout || 5000;

// Cách B
config.timeout = config.timeout !== undefined ? config.timeout : 5000;

// Cách C
config.timeout ??= 5000;

Câu hỏi:

  1. Cách A có bug gì?
  2. Cách B có ưu điểm gì?
  3. Cách C hoạt động như thế nào? Nó giống A hay B?
[Đáp án tham khảo]
  1. Cách A: Bug với 0, "", NaN — tất cả đều falsy nên bị ghi đè bởi default.
  2. Cách B: Explicit, rõ ràng, chỉ ghi đè khi undefined. Không bị falsy values đánh lừa. Dài dòng.
  3. Cách C: ??= (logical nullish assignment) chỉ gán nếu left side là null hoặc undefined. Giống B về semantic, ngắn gọn như A. Đây là lựa chọn tốt nhất nếu team dùng modern syntax.

12. Production Scenario (Tình huống thực tế) ​

Một hệ thống xử lý query parameter từ URL:

js
function parseQuery(params) {
  const limit = params.limit || 20;
  const offset = params.offset || 0;
  const search = params.search || null;

  return { limit, offset, search };
}

User gửi request: ?limit=0&offset=0&search= (muốn lấy tất cả, không filter).

Câu hỏi:

  1. limit sẽ là bao nhiêu? Tại sao đây là bug?
  2. offset sẽ là bao nhiêu?
  3. search sẽ là gì? User muốn tìm chuỗi rỗng hay không tìm gì?
  4. Viết lại hàm để tôn trọng giá trị user truyền vào.
[Đáp án tham khảo]
  1. limit: params.limit là "0" (string từ URL). Nhưng nếu đã parse thành number 0, thì 0 || 20 → 20. BUG: User yêu cầu 0 (không giới hạn) nhưng bị ép thành 20.

  2. offset: 0 || 0 → 0. Đúng về mặt kết quả nhưng fragile.

  3. search: "" || null → null. BUG: User truyền chuỗi rỗng (tìm tất cả) nhưng bị chuyển thành null (không tìm).

  4. Fix:

js
function parseQuery(params) {
  const limit = params.limit !== undefined ? Number(params.limit) : 20;
  const offset = params.offset !== undefined ? Number(params.offset) : 0;
  const search = params.search !== undefined ? params.search : null;

  return { limit, offset, search };
}

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: && và || trả về gì khi cả hai operand đều là non-boolean? Cho ví dụ cụ thể.
  2. Hỏi AI: "What does the && operator return in JavaScript?"
  3. So sánh: AI có nói "returns true or false" không? Hay AI nói "returns the first falsy operand or the last truthy operand"?
  4. Verify: Kiểm tra MDN page "Logical AND (&&)". Xem documentation mô tả return value như thế nào.

Gợi ý

Hãy để ý xem AI có nói && "returns a boolean" không. Đây là một trong những lỗi phổ biến nhất của AI và cả developer junior. Nếu AI nói vậy, bạn đã tìm ra điểm mù.

Đáp án tham khảo
  • Bạn nghĩ

    • && trả về operand, không phải boolean.
    • Nếu left falsy → return left.
    • Nếu left truthy → return right.
    • Ví dụ: "hello" && 42 → 42. 0 && "x" → 0.
  • AI trả lời

    • AI tốt: "The && operator returns the first falsy operand, or the last operand if all are truthy."
    • AI kém: "The && operator returns true if both operands are true, otherwise false."
    • Nhiều AI sẽ nói: "It evaluates operands from left to right and returns the first falsy value. If all are truthy, it returns the last value."
  • So sánh

    • AI tốt sẽ mô tả đúng return value là operand.
    • AI kém sẽ mô tả như boolean logic của toán học, quên rằng JavaScript && trả về actual value.
  • Điểm AI nói sai hoặc quá mơ hồ

    • Câu "returns true or false" là sai về JavaScript semantics. Chỉ có ! luôn trả về boolean.
    • AI có thể nói "used for boolean logic" — đúng trong boolean context, nhưng sai khi dùng cho default values (const x = a || b).
  • Kết luận

    • Nếu bạn chỉ ra được rằng && và || trả về operand và điều này là foundation cho patterns như const name = user && user.name, bạn đã hiểu sâu hơn AI.
    • Concept này sẽ quay lại ở Lesson 0.4.3 (Short-circuit Evaluation) và Stage 8 (React: conditional rendering patterns).

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 const result = 0 || 100 khiến result là 100, nhưng const result = 0 && 100 lại khiến result là 0? Và tại sao cả hai đều không trả về true hoặc false?"

Yêu cầu:

  • Dùng đúng từ: operand, falsy, truthy, short-circuit.
  • Giải thích || và && như "hàm chọn operand".
Mô phỏng
  • Bạn nói
    • "|| và && trong JavaScript không phải là hàm toán học trả về true/false. Chúng là hàm chọn operand."
    • "|| bắt đầu từ trái. Nếu operand trái là truthy, nó trả về ngay operand đó — không cần xem bên phải. Gọi là short-circuit. Với 0 || 100, 0 là falsy nên || phải xem tiếp bên phải, trả về 100."
    • "&& cũng bắt đầu từ trái. Nhưng nó chỉ trả về operand trái nếu nó là falsy. Với 0 && 100, 0 là falsy nên && trả về 0 ngay, không xem 100."
    • "Vì vậy, result nhận giá trị thực tế (100 hoặc 0), không phải true/false. Chỉ khi bạn dùng chúng trong if hoặc !, JavaScript mới coerce kết quả thành boolean."

💡 Liên tưởng: || giống như một người bảo vệ ưu tiên: "Nếu người đầu tiên đủ điều kiện (truthy), cho vào ngay. Nếu không, kiểm tra người thứ hai." && giống như người bảo vệ khắt khe: "Nếu người đầu tiên không đủ điều kiện (falsy), từ chối ngay. Nếu đủ, xem tiếp người thứ hai."

Gợi ý đánh giá bản thân

  • Đồng nghiệp có hỏi "vậy "" || "default" là gì?" không? Nếu có, bạn đã truyền đạt đúng mental model (falsy/truthy, không phải true/false).
  • Bạn có thể dự đoán null && "x" không? (null — falsy, trả về ngay.)

15. Assessment (Đánh giá) ​

Objective (Mục tiêu)Hình thức đánh giá
Phân loại 4 nhóm operatorsClassification (Phân loại)
Giải thích &&/`
Dự đoán kết quả &&/`
Viết code dùng assignment operatorsImplementation (Thực hành)
Debug `

16. Exit Criteria (Tiêu chí qua bài) ​

  • [ ] Có thể phân loại operator thành Arithmetic, Comparison, Logical, Assignment.
  • [ ] Có thể dự đoán đúng 6/6 scenarios trong Prediction Exercise.
  • [ ] Có thể giải thích tại sao && và || trả về operand, không phải boolean.
  • [ ] Có thể viết hàm dùng &&/|| cho default/guard pattern.
  • [ ] Có thể debug trường hợp || ghi đè giá trị 0 hợp lệ.
  • [ ] Không còn nhầm lẫn &&/|| trả về boolean khi dùng ngoài boolean context.

17. Spiral Connection (Liên kết xoắn ốc) ​

Previous (Trước): Module 0.3 — Coercion & Equality. Bạn đã biết == coercion, truthy/falsy, và + string context.

Current (Hiện tại): Lesson 0.4.1 — Operators. Bạn hiểu operators là value-producing actions, &&/|| trả về operand, assignment trả về giá trị.

Next (Tiếp theo):

  • Lesson 0.4.2 — Conditional Logic (if, else, ternary).
  • Lesson 0.4.3 — Short-circuit Evaluation (đào sâu &&, ||, ?? trong patterns thực tế).
  • Lesson 0.4.4 — Guard Clauses & Early Return (dùng logical operators để viết code sạch hơn).
  • Stage 8 — React: Conditional rendering ({show && <Component />} với show = 0 là classic bug từ && operand return).
📴 Offline Mode — Content served from cache