Bài 3 · Cơ bản · 20 phút
Rẽ nhánh & vòng lặp
Biên soạn bởi Nguyễn Anh Tuấn
Điều khiển luồng (control flow) trong JavaScript: if/else, switch, toán tử logic &&/||/??, vòng lặp for/while, break/continue và for...of - kèm bẫy kinh điển.
Chương trình ở Bài 1-2 chạy thẳng một mạch từ trên xuống. Bài này thêm bộ công cụ điều khiển luồng (control flow): rẽ nhánh - chọn việc theo điều kiện, và vòng lặp - chạy lại một khối nhiều lần. Với if, điều kiện là biểu thức được quy về đúng/sai theo luật truthy/falsy ở bài “Biến & kiểu dữ liệu JavaScript” - và so sánh thì luôn dùng ===:
nhánh thử từ trên xuống - và bẫy một dấu =
const diem = 8.5;
if (diem >= 8) {
console.log("Gioi");
} else if (diem >= 6.5) {
console.log("Kha"); // 8.5 cung thoa - nhung nhanh TREN chay truoc
} else {
console.log("Chua dat");
}
let x = 0;
if (x = 5) { // GAN, khong phai so sanh - 5 truthy nen LUON chay
console.log("x bay gio la", x);
} Kết quả khi chạy
Gioi x bay gio la 5
Các nhánh được thử từ trên xuống và chỉ nhánh đúng đầu tiên chạy - đặt >= 6.5 lên trước là điểm 9 cũng thành “Kha”, nên thứ tự nhánh là một quyết định. Khối dưới là bẫy kinh điển: phép gán trả về giá trị vừa gán, nên x = 5 là biểu thức hợp lệ ra 5 - truthy - nhánh luôn chạy còn x bị ghi đè, không một lời báo lỗi. Gặp if “luôn chạy” khó hiểu: soi dấu = trước tiên.
- ▸Điều kiện quy về đúng/sai theo truthy/falsy (Bài 2); so sánh luôn dùng ===.
- ▸Nhánh thử từ trên xuống, chạy nhánh đúng ĐẦU TIÊN - thứ tự nhánh quan trọng.
- ▸if (x = 5) là GÁN: biểu thức ra 5 (truthy) nên nhánh luôn chạy - không hề báo lỗi.
Ghép điều kiện bằng && (và), || (hoặc), ! (phủ định). Hai điều đáng nhớ: chúng trả về một trong hai vế chứ không ép ra true/false, và chúng ngắn mạch (short-circuit) - vế trái đủ quyết định thì vế phải không được tính. Cộng thêm toán tử 3 ngôi ? : - một if/else nằm gọn trong biểu thức:
short-circuit, ?: và ?? - khi 0 là dữ liệu thật
const dangNhap = true;
dangNhap && console.log("Chao mung quay lai"); // "neu ... thi ..."
const diem = 9;
console.log(diem >= 5 ? "Dau" : "Truot"); // toan tu 3 ngoi
const soLanThu = 0; // 0 la du lieu THAT
console.log(soLanThu || 3); // || thay nham ca 0 va ""
console.log(soLanThu ?? 3); // ?? chi thay null/undefined Kết quả khi chạy
Chao mung quay lai Dau 3 0
Toán tử mới ?? (nullish coalescing) chỉ coi null/undefined là “chưa có”. Khác biệt với ||: 0 và "" là falsy thật nhưng nhiều khi là dữ liệu hợp lệ - điểm 0 vẫn là điểm.
- ▸&& và || ngắn mạch: vế phải chỉ chạy khi cần - đọc a && f() là “nếu a thì gọi f”.
- ▸Toán tử 3 ngôi ?: là if/else dạng biểu thức - hợp phép chọn ngắn, đừng lồng nhiều tầng.
- ▸Mặc định cho dữ liệu có thể là 0/"" → dùng ??; còn || sẽ ghi đè nhầm chúng.
Khi so một giá trị với nhiều khả năng cụ thể, chuỗi else-if dài khó đọc - switch sinh ra cho việc đó. Nó so sánh nghiêm ngặt bằng ===, chạy từ case khớp tới khi gặp break, còn default hứng phần còn lại. Hai case viết liền nhau là cách gộp case cố ý:
điều khiển nhân vật bằng phím
const phim = "w";
switch (phim) {
case "w":
console.log("Di len");
break;
case "a":
case "d": // gop 2 case = fall-through CO Y
console.log("Di ngang");
break;
default:
console.log("Phim khong ho tro");
} Kết quả khi chạy
Di len
Còn quên break là dính fall-through (chảy xuyên) ngoài ý muốn: chạy xong một case, máy chạy tiếp case bên dưới mà không so sánh gì nữa:
quên break - chạy xuyên xuống case dưới
const mon = "toan";
switch (mon) {
case "toan":
console.log("Hoc toan"); // quen break o day...
case "tin":
console.log("Hoc tin"); // ...nen chay xuyen xuong day
} Kết quả khi chạy
Hoc toan Hoc tin
- ▸switch so sánh === - switch ("1") không khớp case 1.
- ▸Mỗi nhóm case kết thúc bằng break; quên break là fall-through ngoài ý muốn.
- ▸So một biến với danh sách giá trị cụ thể → switch; điều kiện khoảng (diem >= 8) → else if.
Vòng lặp chạy lại một khối khi điều kiện còn đúng. Câu lệnh for gói 4 mảnh: khởi tạo (chạy đúng một lần) → điều kiện (kiểm TRƯỚC mỗi vòng) → thân → cập nhật. Bấm Bước để xem máy đi qua từng mảnh - đây chính là thứ một debugger cho bạn thấy:
Hai quan sát: thân có thể chạy 0 lần (thử n = 0), và vòng lặp chỉ dừng khi điều kiện sai. while là for chỉ giữ lại phần điều kiện - hợp khi chưa biết trước số vòng; còn do...while kiểm tra SAU nên thân chạy ít nhất một lần:
while: lặp đến khi đạt mục tiêu
let von = 100;
let nam = 0;
while (von < 200) { // bao gio von gap doi? chua biet truoc so vong
von = von * 1.07; // lai kep 7%/nam
nam = nam + 1;
}
console.log(nam, "nam ->", von.toFixed(2)); Kết quả khi chạy
11 nam -> 210.49
Trong thân có hai lệnh điều khiển: break dừng hẳn vòng lặp, còn continue bỏ phần còn lại của vòng hiện tại, nhảy sang vòng kế:
continue bỏ qua một vòng · break dừng hẳn
const giu = [];
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) continue; // chan -> bo qua, sang vong ke
if (i > 7) break; // qua 7 -> dung han vong lap
giu.push(i);
}
console.log(giu); Kết quả khi chạy
[ 1, 3, 5, 7 ]
Bẫy 1 - vòng lặp vô tận
Bẫy thứ hai tinh vi hơn - off-by-one (lệch một đơn vị): nhầm giữa < và <= làm vòng lặp chạy thừa hoặc thiếu đúng một lần:
một dấu = lệch một vòng
let lan1 = 0, lan2 = 0;
for (let i = 0; i < 5; i++) lan1++; // i = 0..4 -> 5 vong
for (let i = 0; i <= 5; i++) lan2++; // i = 0..5 -> 6 vong
console.log(lan1, lan2); Kết quả khi chạy
5 6
- ▸for = khởi tạo → điều kiện → thân → cập nhật; kiểm TRƯỚC nên thân có thể chạy 0 lần.
- ▸Biết trước số vòng → for; chưa biết → while; cần chạy ít nhất một lần → do...while.
- ▸break dừng hẳn; continue sang vòng kế. Hai bẫy phải soi: lặp vô tận và off-by-one.
Duyệt mảng bằng for + chỉ số vẫn được, nhưng phải nuôi biến đếm và dễ off-by-one. Cách hiện đại: for...of lấy thẳng từng giá trị - mảng hay chuỗi đều duyệt được. Đừng nhầm với for...in: nó lấy key, mà key của mảng lại là chuỗi - nó sinh ra cho object, không nên dùng với mảng:
of lấy GIÁ TRỊ · in lấy KEY (chuỗi)
const dsMeo = ["Mun", "Xam"];
for (const ten of dsMeo) { // of: lay GIA TRI
console.log("meo", ten);
}
for (const k in dsMeo) { // in: lay KEY - va key la CHUOI
console.log(k, typeof k);
} Kết quả khi chạy
meo Mun meo Xam 0 string 1 string
Key dạng chuỗi cộng với chỉ số là dính ngay bẫy nối chuỗi của Bài 2. Một tin tốt: sang bài “Mảng & object” (bài 5) bạn sẽ gặp map/filter - nhiều vòng lặp viết tay hôm nay sẽ tự biến mất.
- ▸for...of duyệt GIÁ TRỊ của mảng/chuỗi - không biến đếm, không off-by-one.
- ▸for...in duyệt KEY dạng chuỗi, sinh ra cho object - tránh dùng cho mảng.
- ▸Cần cả chỉ số lẫn giá trị → for...of với entries(), gặp ở bài Mảng & object.
Tiếp theo
Câu hỏi thường gặp
Bằng === - nghiêm ngặt, không ép kiểu. Vì vậy switch ("1") KHÔNG khớp case 1: chuỗi khác số là trượt luôn, không có chuyện "1" được tự đổi thành 1. Đây là điểm cộng của switch: nó tránh sẵn các bẫy ép kiểu của == đã học ở Bài 2.
Biết trước số vòng (đếm 1..n, duyệt hết một mảng) → for hoặc for...of. Chưa biết trước, chỉ biết "lặp đến khi đạt điều kiện" (chờ nhập đúng mật khẩu, chờ vốn gấp đôi) → while. Còn do...while dành cho việc phải làm ÍT NHẤT một lần rồi mới biết có làm tiếp không, ví dụ hỏi "chơi ván nữa không?".
Không hỏng máy - nhưng tab trình duyệt hoặc process Node sẽ đứng hình vì bận chạy mãi không thoát. Trình duyệt: đóng tab; Node: nhấn Ctrl+C trong terminal. Phòng tránh: nhìn thân vòng lặp và tự hỏi "biến trong điều kiện CÓ tiến về phía làm điều kiện sai không?" - nếu không, đó là vòng lặp vô tận.
Gần như nguyên xi: JS thừa hưởng cú pháp for/while/do...while và break/continue từ C. Hai khác biệt đáng nhớ: JS có thêm for...of, và let trong for tạo một biến MỚI cho mỗi vòng - chi tiết này thành quan trọng khi gặp closure ở bài 4.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Mèo con viết let x = 0; rồi if (x = 5) { console.log("vao nhanh"); }. Chuyện gì xảy ra?
- 1
FizzBuzz kiểu mèo
In các số 1-20: chia hết cho 3 in
"Meo", chia hết cho 5 in"Gru", chia hết cả hai in"MeoGru", còn lại in chính số đó.Hoàn thành khi: 3 → Meo, 5 → Gru, 15 → MeoGru, 20 → Gru. Nhánh "chia hết cả hai" phải đứng TRÊN CÙNG - đặt sau là 15 chỉ ra Meo.
- 2
?? vs ||
Đoán trước rồi chạy:
0 || 5,0 ?? 5,"" || "meo","" ?? "meo",null ?? "meo".Hoàn thành khi: Ra
5, 0, "meo", "", "meo"-||thay cả0lẫn"";??chỉ thaynull/undefined. - 3
switch ngày trong tuần
Viết
switch (ngay): các chuỗi"t2"…"t6"in"Ngay di hoc", gộp"t7"và"cn"in"Cuoi tuan",defaultin"Khong hop le".Hoàn thành khi:
"t4"→ Ngay di hoc;"cn"→ Cuoi tuan;"t9"→ Khong hop le. Mỗi nhóm case kết thúc bằngbreak. - 4
Bảng cửu chương
Dùng
forin bảng cửu chương 7, dạng7 x 1 = 7…7 x 10 = 70.Hoàn thành khi: Đúng 10 dòng, không thừa không thiếu; dòng cuối là
7 x 10 = 70. - 5
Thân chạy 0 lần
Dùng công cụ ở Bước 4 với
n = 0: thân vòng lặp chạy mấy lần? Vì sao?Hoàn thành khi: 0 lần - lần kiểm tra đầu tiên
1 <= 0đã sai nên nhảy thẳng tới kết thúc;tonggiữ nguyên 0. - 6
Sửa vòng lặp treo
Đoạn
let i = 1; while (i <= 5) { console.log(i); }chạy mãi không dừng. Mèo con sửa để nó in 1-5 rồi thoát.Hoàn thành khi: Thêm
i++vào thân: in1 2 3 4 5(mỗi số một dòng) rồi chương trình tự kết thúc, không cần Ctrl+C.