Học bằng cách đọc theo thứ tự

Lặp theo điều kiện — while, do...while, break và continue

Học while và do...while trong JavaScript: lặp khi không biết trước số lần, cách tránh vòng lặp vô hạn, và khi nào dùng continue, khi nào dùng break.

Xếp chỗ cho người trong danh sách chờ khi có chỗ trống, hay mua thêm hàng chừng nào số dư còn đủ — với những việc như vậy, bạn biết khi nào cần dừng, nhưng không biết trước phải lặp bao nhiêu lần.

Bài này nói về whiledo...while để lặp dựa trên điều kiện, continue để bỏ qua một vòng, và break để thoát khỏi vòng lặp.

Lặp mà không cố định số lần — while và việc cập nhật điều kiện

Giả sử có vài chỗ ngồi vừa trống, và bạn muốn lần lượt xếp chỗ cho người ở đầu danh sách chờ. Mỗi người được xếp sẽ chiếm một chỗ, và bạn dừng lại khi còn 0 chỗ. Thứ khiến vòng lặp dừng là một trạng thái — "chừng nào còn chỗ trống" — chứ không phải số vòng, nên vòng lặp for vừa đếm vừa lặp không diễn đạt được điều này một cách tự nhiên.

Vòng lặp while (câu lệnh lặp lại phần thân trong cặp ngoặc nhọn chừng nào điều kiện trong cặp ngoặc tròn còn đúng) chỉ nhận một điều kiện trong cặp ngoặc tròn. Điều kiện được kiểm tra ở mỗi vòng, trước khi vào thân.

Bạn tự đặt giá trị ban đầu ở trước vòng lặp, và tự cập nhật biến mà điều kiện kiểm tra ở bên trong thân.

const waitingList = ["Minh", "Linh", "Hùng"];   // danh sách chờ
let openSeats = 2;                              // số chỗ còn trống

// Chỉ xếp chỗ khi còn chỗ trống
while (openSeats > 0) {
  const name = waitingList.shift();
  openSeats--;   // tương đương openSeats = openSeats - 1
  console.log(`Đã xếp chỗ cho ${name} (còn ${openSeats} chỗ)`);
}
// Đã xếp chỗ cho Minh (còn 1 chỗ)
// Đã xếp chỗ cho Linh (còn 0 chỗ)

console.log(`Số người còn chờ: ${waitingList.length}`);   // Số người còn chờ: 1
Vòng lặp while kết thúc và vòng lặp while không kết thúc
Điều kiệnopenSeats > 0Thân xếp chỗcho một ngườiopenSeats--có cập nhậtVề 0: điều kiệnthành falseĐiều kiệnopenSeats > 0Thân xếp chỗcho một ngườiKhông cập nhậtGiữ nguyên 2,không bao giờ dừng
Nếu thân vòng lặp giảm biến mà điều kiện kiểm tra, điều kiện cuối cùng sẽ thành false và vòng lặp kết thúc. Nếu không giảm, lần kiểm tra nào cũng cho cùng một kết quả và thân cứ tiếp tục chạy.

Trong vòng lặp for, biểu thức cập nhật có chỗ cố định bên trong cặp ngoặc tròn, còn với while thì bạn tự quyết định đặt nó ở đâu trong thân. Mặt trái của sự tự do đó là không có gì ngăn bạn quên viết nó: thiếu phần cập nhật thì code vẫn hoàn toàn đúng cú pháp.

Quên cập nhật thì vòng lặp không bao giờ dừng

Nếu điều kiện không bao giờ thành false, thân sẽ lặp lại mãi mãi. Nếu quên viết openSeats--, số chỗ vẫn giữ nguyên là 2, nên lần kiểm tra nào cũng cho cùng một kết quả. Console sẽ báo timeout sau 15 giây nhưng vòng lặp vẫn không dừng, và các bài tập khác cũng không chạy được cho tới khi bạn tải lại trang. Luôn thay đổi biến mà điều kiện kiểm tra ở đâu đó trong thân.

Dùng số điểm tích lũy để mua cùng một món nhiều nhất có thể. balance và unitPrice đã được khai báo sẵn.

① Hãy chuẩn bị một biến bắt đầu từ 0 để đếm số món đã mua.

② Hãy viết điều kiện của vòng lặp sao cho nó chỉ lặp khi số dư còn đủ mua thêm một món.

③ Ở mỗi vòng, hãy giảm số dư, tăng số món đã mua, và in ra "Món thứ ◯ (còn ◯ điểm)".

④ Sau vòng lặp, hãy in số món đã mua và số điểm còn lại trên một dòng.

(Nếu chạy đúng, phần giải thích sẽ xuất hiện.)

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Chạy trước một lần — thứ tự kiểm tra của do...while

Khi gửi thông báo thất bại, bạn sẽ gửi lại vài lần cho tới khi thành công. Bạn phải gửi ít nhất một lần thì mới có kết quả để kiểm tra, nhưng while kiểm tra điều kiện trước khi vào thân, nên tùy vào giá trị ban đầu của biến trong điều kiện, vòng lặp có thể kết thúc mà không gửi lần nào.

Vòng lặp do...while (câu lệnh chạy thân một lần rồi mới kiểm tra điều kiện) được viết là do { thân } while (điều_kiện);, và toàn bộ phần tính đến dấu chấm phẩy cuối cùng là một câu lệnh. Vì việc kiểm tra đứng sau thân, thân vẫn chạy một lần kể cả khi điều kiện sai ngay từ đầu.

const attempts = [false, false, true];   // kết quả gửi được chuẩn bị sẵn (true là thành công)
let sent = true;                         // vẫn còn là true từ thông báo trước
let tried = 0;

// while kiểm tra trước, nên kết thúc mà không gửi lần nào
while (!sent && tried < attempts.length) {
  sent = attempts[tried];
  tried++;
}
console.log(`Số lần gửi với while: ${tried}`);   // Số lần gửi với while: 0

// do...while gửi lần đầu trước khi kiểm tra, rồi dựa vào kết quả để quyết định có tiếp tục không
do {
  sent = attempts[tried];
  tried++;
  console.log(`Lần gửi thứ ${tried}: ${sent ? "thành công" : "thất bại"}`);
} while (!sent && tried < attempts.length);
// Lần gửi thứ 1: thất bại / Lần gửi thứ 2: thất bại / Lần gửi thứ 3: thành công
while và do...while khác nhau ở điểm bắt đầu
Bắt đầu vớisent = trueKiểm tra trước!true là falseKhông vàothânĐã gửi 0 lầnBắt đầu vớisent = trueChạy thân trước,gửi lần thứ 1Kết quả là false,!false: chạy tiếpKết thúc khi lầnthứ 3 thành công
Cả hai vòng lặp đều bắt đầu với sent vẫn còn là true. while kết thúc ngay sau lần kiểm tra, còn do...while gửi một lần rồi mới quyết định có tiếp tục không.

Chỉ vòng đầu tiên là bỏ qua bước kiểm tra; từ đó trở đi, vòng lặp chỉ lặp lại khi điều kiện còn đúng, giống hệt while. Nếu bạn không muốn thân chạy lần nào khi điều kiện sai, hãy chọn while. Nếu muốn thân chạy một lần trước khi xem kết quả, hãy chọn do...while.

Gán số đặt phòng nhỏ nhất chưa được dùng cho một lượt đặt phòng họp. Mảng usedNumbers (các số đã được dùng) đã được khai báo sẵn.

① Hãy chuẩn bị một biến bắt đầu từ 0 để chứa số sẽ gán.

② Hãy dùng vòng lặp chạy thân trước rồi mới kiểm tra; trong thân, tăng số đó thêm 1 và in ra "Kiểm tra số ◯".

③ Hãy viết điều kiện để vòng lặp tiếp tục chừng nào số đó còn nằm trong mảng các số đã dùng.

④ Sau vòng lặp, hãy in ra "Số đặt phòng: ◯".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Chỉ bỏ qua một vòng — khi nào dùng continue

Khi xử lý danh sách xuất hàng từ trên xuống, bạn sẽ muốn bỏ qua những dòng đã hết hàng và chuyển sang dòng tiếp theo. Càng thêm nhiều điều kiện bỏ qua, các cặp ngoặc nhọn của if càng lồng sâu, và phần code xuất hàng mà bạn thực sự quan tâm bị chôn vùi bên trong.

Với continue (câu lệnh bỏ qua phần còn lại của vòng hiện tại và chuyển sang vòng tiếp theo), bạn có thể kết thúc sớm một vòng ngay khi gặp điều kiện muốn bỏ qua. Bản thân vòng lặp vẫn tiếp tục, nên vòng tiếp theo bắt đầu bình thường bằng việc kiểm tra điều kiện.

const names = ["Sổ tay A5", "Đèn bàn", "Hub USB"];
const stocks = [12, 0, 5];   // 0 nghĩa là hết hàng
let index = 0;

while (index < names.length) {
  const name = names[index];
  const stock = stocks[index];
  index++;   // tăng trước để continue không bỏ qua dòng này

  // Dòng hết hàng thì bỏ qua phần còn lại của vòng này và sang vòng tiếp theo
  if (stock === 0) {
    console.log(`${name}: hết hàng, bỏ qua`);
    continue;
  }
  console.log(`${name}: xuất ${stock} cái`);
}
// Sổ tay A5: xuất 12 cái
// Đèn bàn: hết hàng, bỏ qua
// Hub USB: xuất 5 cái
Dòng được xử lýGiá trị của stock và kết quả kiểm traKết quả của vòng đó
Dòng 1: Sổ tay A5stock là 12, khác 0In ra xuất 12 cái
Dòng 2: Đèn bànstock là 0 nên continueDòng xuất hàng không chạy
Dòng 3: Hub USBstock là 5, khác 0In ra xuất 5 cái

continue chỉ bỏ qua các dòng nằm giữa nó và cuối thân. Bạn cũng có thể dùng continue trong thân của forfor...of, và vì biểu thức cập nhật trong cặp ngoặc tròn của for nằm ngoài thân, nó vẫn chạy ở vòng đã gặp continue.

continue cũng bỏ qua luôn dòng cập nhật

Nếu đặt index++ sau continue, index sẽ không tăng ở vòng bị bỏ qua. Vòng tiếp theo lại đọc đúng dòng đó, stock vẫn là 0, điều kiện không thay đổi, và vòng lặp không bao giờ kết thúc. Hãy cập nhật biến của điều kiện ở trước dòng continue.

Tính điểm trung bình của các review, loại bỏ những giá trị không dùng được để tính. Mảng scores đã được khai báo sẵn.

① Hãy chuẩn bị các biến bắt đầu từ 0 cho tổng và số lượng.

② Hãy dùng for...of để lấy lần lượt từng điểm.

③ Với 0 (chưa đánh giá) và mọi giá trị lớn hơn 5, hãy in ra "Loại bỏ ◯" và bỏ qua phần còn lại của vòng đó.

④ Hãy cộng các điểm còn lại vào tổng và số lượng, và sau vòng lặp in ra "Trung bình: ◯.◯ (◯ review)".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Dừng ngay khi tìm thấy — break thoát ra tới đâu

Giả sử bạn muốn quét một file log từ trên xuống và chỉ tìm lỗi đầu tiên. Nếu vẫn đọc tiếp tới cuối sau khi đã tìm thấy thì đó là việc thừa, và tùy cách viết, một lỗi phía sau có thể ghi đè lên firstError.

Với break (câu lệnh kết thúc ngay lập tức vòng lặp đang chứa nó), bạn có thể thoát khỏi vòng lặp ở vòng mà điều kiện được thỏa. Đây cũng chính là câu lệnh bạn đã dùng để thoát khỏi switch trong bài về rẽ nhánh điều kiện. Vì bạn sẽ dùng giá trị tìm được ở bên ngoài vòng lặp, hãy khai báo firstError trước vòng lặp.

const logs = ["INFO Đã khởi động", "WARN Phản hồi chậm", "ERROR Thanh toán thất bại", "ERROR Gửi lại thất bại"];
let index = 0;
let firstError = "";

while (index < logs.length) {
  const line = logs[index];
  index++;
  if (line.startsWith("ERROR")) {
    firstError = line;
    break;   // đã tìm thấy lỗi đầu tiên nên không kiểm tra phần còn lại
  }
}

console.log(`Lỗi đầu tiên: ${firstError}`);   // Lỗi đầu tiên: ERROR Thanh toán thất bại
console.log(`Số dòng đã kiểm tra: ${index}`); // Số dòng đã kiểm tra: 3
Cặp ngoặc nhọn mà break thoát ra
Cặp ngoặc nhọn của while (kiểm tra log từng dòng)
  • const line = logs[index] — dòng được kiểm tra ở vòng này
  • Sau break, chương trình ra khỏi cặp ngoặc nhọn và đi tới dòng console.log kế tiếp
Cặp ngoặc nhọn của if (chỉ các dòng bắt đầu bằng ERROR)
  • firstError = line — lưu lại dòng tìm được
  • break — thoát khỏi vòng while bên ngoài, chứ không phải cặp ngoặc này
  • Các dòng nằm dưới break sẽ không bao giờ chạy
Cả các dòng trong thân nằm dưới break lẫn các vòng còn lại đều không chạy. Chương trình tiếp tục ở dòng nằm sau dấu ngoặc nhọn đóng của vòng lặp while.

Ngay cả ở vòng gặp break, dòng index++ viết trước nó cũng đã chạy xong. Vì vậy index dừng ở 3, và "Số dòng đã kiểm tra: 3" in ra sau vòng lặp trùng với vị trí của dòng tìm được, tính từ 1.

continue và break khác nhau ở đâu
continue khistock === 0Kiểm tra lạiindex < 3Chuyển sang vòngcủa Hub USBIn raxuất 5 cáibreak ở dòngERROR đầu tiênKhông kiểm traindex < 4Không đọc dòngERROR thứ 4Đến dòng in ralỗi đầu tiên
Cả hai đều không chạy các dòng còn lại trong vòng đó. continue quay lại bước kiểm tra điều kiện; break thoát khỏi vòng lặp mà không kiểm tra.

Phân bổ hàng tồn kho cho các đơn theo thứ tự nhận đơn, và dừng lại ở đơn đầu tiên không đủ hàng. orderQuantities (số lượng của từng đơn) và stock (số hàng tồn kho) đã được khai báo sẵn.

① Hãy dùng for...of để lấy lần lượt số lượng của từng đơn.

② Ở vòng mà số lượng lớn hơn tồn kho, hãy in ra "Không đủ hàng cho đơn ◯ cái" và kết thúc vòng lặp.

③ Ở vòng còn đủ hàng, hãy giảm tồn kho và in ra "Đã phân bổ ◯ cái (còn ◯ cái)".

④ Sau vòng lặp, hãy in ra "Tồn kho còn lại: ◯ cái".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Nếu một vòng lặp while bắt đầu khi điều kiện đang đúng và thân không bao giờ thay đổi biến mà điều kiện kiểm tra, chuyện gì xảy ra?

Câu 2Nếu điều kiện sai ngay từ đầu, thân của do...while chạy bao nhiêu lần?

Câu 3Khi continue chạy bên trong một vòng lặp, chuyện gì xảy ra tiếp theo?