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

Vòng lặp — for, for...of và for...in

Học vòng lặp trong JavaScript từ nền tảng: for để đếm số lần, for...of để lấy lần lượt từng phần tử, for...in để duyệt key, và cách viết vòng lặp lồng nhau.

Để in giá của từng món hàng trong đơn ra một dòng riêng, bạn sẽ phải viết cùng một dòng code 3 lần nếu đơn có 3 món, và 10 lần nếu có 10 món. Số món phụ thuộc vào dữ liệu, nên bạn không thể biết trước mình cần bao nhiêu dòng.

Bài này nói về for để lặp bằng cách đếm, for...of để lấy lần lượt từng phần tử, và for...in để duyệt qua các key.

Lặp lại code cho từng món — ba biểu thức của for

Giả sử bạn muốn in tên các món trong một đơn hàng, mỗi món một dòng. Nếu viết lần lượt từng dòng, bắt đầu từ console.log(orderLines[0]), thì khi đơn hàng tăng lên 4 món bạn phải thêm một dòng, và cứ mỗi lần số món thay đổi là phải sửa code.

Vòng lặp for (câu lệnh vừa đếm vừa lặp lại cùng một đoạn code) nhận ba biểu thức trong cặp ngoặc tròn, ngăn cách bằng dấu chấm phẩy, còn đoạn code muốn lặp lại thì đặt trong cặp ngoặc nhọn. Phần nằm trong cặp ngoặc nhọn đó gọi là thân vòng lặp (body). Từ trái sang phải, ba biểu thức lần lượt là khởi tạo, điều kiện và cập nhật: điều kiện được kiểm tra trước khi vào thân, còn cập nhật chạy sau khi thân chạy xong.

i++ có cùng ý nghĩa với i = i + 1. Vì giá trị của i thay đổi sau mỗi vòng, i được khai báo bằng let.

const orderLines = ["Cốc sứ", "Sổ tay", "Bút bi"];   // tên các món trong đơn hàng

// Bắt đầu i từ 0 và tăng thêm 1 bằng i++ cho tới khi bằng số món
for (let i = 0; i < orderLines.length; i++) {
  // Index bắt đầu từ 0, nên hiển thị i + 1 để đếm từ món thứ 1
  console.log(`Món ${i + 1}: ${orderLines[i]}`);
}
// Món 1: Cốc sứ
// Món 2: Sổ tay
// Món 3: Bút bi

console.log(`Số món đã in: ${orderLines.length}`);   // Số món đã in: 3
Một vòng của vòng lặp for
Khởi tạolet i = 0Điều kiệni < 3Thânin 1 mónCập nhậti++Kết thúcvòng lặptruekiểm tra lạifalse

Khởi tạo chỉ chạy một lần duy nhất lúc bắt đầu, điều kiện chạy trước thân, và cập nhật chạy sau thân. Ở vòng mà điều kiện trở thành false, vòng lặp kết thúc mà không chạy thân.

Mỗi lượt kiểm tra điều kiện, chạy thân rồi chạy cập nhật được gọi là một vòng (iteration). Vì cập nhật chạy sau thân, i luôn bằng số lần thân đã chạy xong trước đó, nên ở vòng đầu tiên thân chạy khi i vẫn còn là 0.

Bảng dưới đây cho thấy giá trị i và kết quả kiểm tra điều kiện ở từng vòng.

VòngGiá trị của i và điều kiệnĐiều xảy ra trong thân
Vòng 1i = 0 / 0 < 3 là trueMón 1: Cốc sứ
Vòng 2i = 1 / 1 < 3 là trueMón 2: Sổ tay
Vòng 3i = 2 / 2 < 3 là trueMón 3: Bút bi
Đầu vòng 4i = 3 / 3 < 3 là falseThân không chạy, vòng lặp kết thúc

Đọc vượt quá cuối mảng sẽ nhận undefined

Nếu viết điều kiện là i <= orderLines.length, vòng lặp sẽ chạy thêm một vòng ở cuối và đọc orderLines[3] — một phần tử không tồn tại. Mọi vị trí ngoài phạm vi đều là undefined, nên màn hình in ra "Món 4: undefined", và nếu vòng lặp đang cộng dồn giá tiền thì kết quả thành NaN. Chỉ cho vòng lặp chạy khi index còn nhỏ hơn số món.

Cộng giá các món hàng trong giỏ hàng. Mảng giá unitPrices đã được khai báo sẵn.

① Hãy chuẩn bị một biến chứa tổng, bắt đầu từ 0.

② Hãy dùng for để đếm từ 0 trở lên, và cộng từng giá vào tổng.

③ Hãy in tổng theo dạng "Tổng: ◯◯ yên".

④ Hãy chia tổng cho số món để ra giá trung bình, làm tròn xuống, rồi in theo dạng "Trung bình: ◯◯ yên".

(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

Lấy thẳng từng phần tử — for...of và entries

Giả sử bạn muốn xử lý lần lượt danh sách mã đơn hàng đang chờ gửi. Với for, bạn phải khai báo biến index, viết điều kiện và cập nhật, rồi đọc từng mã bằng orderIds[i] — và chỉ cần gõ sai index là đọc nhầm sang phần tử khác.

for...of (câu lệnh lấy lần lượt từng phần tử của mảng hoặc đối tượng iterable khác và đưa vào một biến) được viết là for (const orderId of orderIds), trong cặp ngoặc tròn chỉ có phần khai báo và mảng.

Khi cần cả vị trí, bạn lặp qua orderIds.entries(). Mỗi vòng nó đưa ra một cặp như [0, "A-1001"], và bạn tách cặp đó bằng cặp ngoặc vuông thành [index, orderId].

const orderIds = ["A-1001", "A-1002", "A-1003"];   // mã các đơn hàng đang chờ gửi

// orderId chứa chính phần tử (không thấy index đâu)
for (const orderId of orderIds) {
  console.log(`Chờ gửi hàng: ${orderId}`);
}
// Chờ gửi hàng: A-1001 … (3 dòng)

// Khi cần cả index, lấy từng cặp từ entries()
for (const [index, orderId] of orderIds.entries()) {
  console.log(`#${index + 1}: ${orderId}`);
}
// #1: A-1001
// #2: A-1002
// #3: A-1003
Cách lấyGiá trị trong biếnDòng được in ra
Lấy phần tử bằng ofChính phần tử đó (A-1001)Chờ gửi hàng: A-1001
Tách entries() thành [index, orderId]index là 0 / orderId là A-1001#1: A-1001
Nhận entries() vào một biếnCặp index và phần tửVẫn là mảng [0, "A-1001"]

const [index, orderId] hoạt động giống cách tách mảng trong bài về destructuring: index vào vị trí thứ nhất, phần tử vào vị trí thứ hai. Mỗi vòng đều tạo ra một biến mới, nên bạn có thể khai báo biến của vòng lặp for...of bằng const mà không bị lỗi gán lại.

Tìm các đơn hàng chưa được giao trong danh sách trạng thái giao hàng. Mảng deliveries đã được khai báo sẵn; mỗi đơn hàng có mã đơn (id) và trạng thái (status).

① Hãy dùng for...of để lấy lần lượt từng đơn hàng và in mỗi đơn theo dạng "A-1001: Đã giao".

② Hãy lặp lại một lần nữa, lần này lấy cả vị trí, và chỉ in các đơn chưa giao theo dạng "#2 A-1002: Đang giao".

③ Hãy đếm số đơn đã in ở ②, và cuối cùng in ra "Chưa giao: ◯".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Duyệt qua các key — for...in và lý do không dùng với mảng

Đôi khi bạn muốn in cả tên key lẫn giá trị cạnh nhau, chẳng hạn với hồ sơ thành viên. Bạn có thể chuyển các key thành mảng bằng Object.keys(profile) rồi lặp bằng for...of, nhưng nếu chỉ cần duyệt qua các key thì có cách viết ngắn hơn.

for...in (câu lệnh lấy lần lượt từng key của một object) được viết là for (const key in profile). Biến chứa key dưới dạng chuỗi chứ không phải giá trị, nên bạn đọc giá trị bằng cách viết ngoặc vuông (bracket notation) như profile[key].

const profile = { displayName: "Minh", plan: "premium", posts: 12 };

// key chứa key dưới dạng chuỗi (không phải giá trị)
for (const key in profile) {
  console.log(`${key}: ${profile[key]}`);
}
// displayName: Minh
// plan: premium
// posts: 12

// Với mảng, index được đưa ra dưới dạng chuỗi
const stockCounts = [5, 0, 12];
for (const key in stockCounts) {
  console.log(`${typeof key} / ${key + 1}`);
}
// string / 01
// string / 11
// string / 21   ← bị nối chuỗi chứ không cộng như số
for...in đưa ra các key
Objectprofilekey làchuỗi tên keyĐọc giá trịbằng profile[key]plan: premiumMảngstockCountskey là"0" "1" "2"key + 1là nối chuỗiThành "01"
Với object, bạn nhận từng key dưới dạng chuỗi; với mảng, bạn nhận từng index dưới dạng chuỗi. Cộng 1 với index của mảng không phải phép tính số học mà là nối chuỗi.

Nếu chỉ đọc phần tử, như stockCounts[key], thì chuỗi "1" vẫn lấy ra được giá trị. Bạn có thể chuyển nó bằng Number(key) để dùng trong phép tính, nhưng với phần tử của mảng thì chọn ngay từ đầu một kiểu vòng lặp khác sẽ đơn giản hơn. Bảng dưới đây cho thấy khi nào nên dùng từng cách trong ba cách.

Cú phápGiá trị trong biếnPhù hợp khi
for (let i = 0; i < số_lượng; i++)Index dạng sốBạn muốn dùng vị trí trong phép tính
for (const item of mảng)Chính phần tử đóBạn chỉ cần xử lý các phần tử theo thứ tự
for (const key in object)Key dạng chuỗiBạn cần duyệt qua các key của một object

Không thể lặp qua object bằng for...of

Nếu đặt một object bên phải of, như for (const item of profile), chương trình sẽ dừng với TypeError: profile is not iterable. Object thông thường không phải là iterable, vì nó không định nghĩa cách đưa ra lần lượt từng nội dung bên trong. Để duyệt qua cả giá trị, hãy chuyển object thành mảng bằng Object.entries trước.

Trên màn hình chỉnh sửa hồ sơ, so sánh hồ sơ trước và sau khi lưu, rồi chỉ in những mục đã thay đổi. before (giá trị cũ) và after (giá trị mới) đã được khai báo sẵn.

① Hãy chuẩn bị một biến bắt đầu từ 0 để đếm số mục đã thay đổi.

② Hãy dùng for...in để lấy lần lượt từng key của before.

③ Hãy đọc giá trị mới bằng cùng key đó, rồi với những mục có giá trị khác nhau, hãy in theo dạng "city: Yokohama -> Kawasaki" và đếm chúng.

④ Cuối cùng, hãy in ra "Đã thay đổi ◯ mục".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Liệt kê mọi tổ hợp — vòng lặp trong và vòng lặp ngoài

Giả sử bạn muốn liệt kê mỗi cửa hàng bán những sản phẩm nào, dưới dạng mọi tổ hợp cửa hàng và sản phẩm. Một vòng lặp đơn chỉ đi qua được hoặc các cửa hàng, hoặc các sản phẩm, chứ không đi qua cả hai cùng lúc. Hơn nữa, cả số cửa hàng lẫn số sản phẩm đều phụ thuộc vào dữ liệu.

Bạn có thể đặt một vòng lặp khác ngay bên trong thân của một vòng lặp. Trong mỗi vòng của vòng lặp ngoài, vòng lặp trong chạy trọn từ đầu đến cuối, nên số lần thân vòng lặp trong được chạy là số vòng ngoài nhân với số vòng trong. Vòng lặp trong cũng đọc được trực tiếp biến của vòng lặp ngoài.

const stores = ["Shibuya", "Yokohama"];
const products = ["Cốc sứ", "Sổ tay"];

// Trong mỗi vòng của vòng lặp ngoài, vòng lặp trong chạy trọn từ đầu đến cuối
for (const store of stores) {
  for (const product of products) {
    console.log(`${store} / ${product}`);
  }
}
// Shibuya / Cốc sứ
// Shibuya / Sổ tay
// Yokohama / Cốc sứ
// Yokohama / Sổ tay
Những biến mà vòng lặp trong đọc được
for...of ngoài (mỗi cửa hàng một vòng)
  • store — đổi từ "Shibuya" sang "Yokohama" ở mỗi vòng
  • Thân vòng ngoài chỉ chạy 2 lần
for...of trong (chạy hết products)
  • product — đổi từ "Cốc sứ" sang "Sổ tay" ở mỗi vòng trong
  • Đọc được trực tiếp store của vòng ngoài
  • Riêng thân này chạy 2 × 2 = 4 lần
Thân vòng lặp trong đọc được cả product của vòng trong lẫn store của vòng ngoài. product được khai báo trong vòng lặp trong nên chỉ dùng được bên trong cặp ngoặc nhọn của vòng trong.

for...of bên trong bắt đầu lại từ đầu products mỗi khi vòng ngoài chuyển sang vòng mới, nên khi store vẫn giữ nguyên thì chỉ có product thay đổi. Biến khai báo ở đầu thân vòng ngoài cũng được tạo mới ở mỗi vòng ngoài. Cặp ngoặc nhọn giới hạn phạm vi dùng được của biến như thế nào sẽ được nói ở bài về scope.

Cộng giá các món trong từng đơn hàng. Mảng orders đã được khai báo sẵn; mỗi đơn hàng có mã đơn (id) và mảng giá các món (prices).

① Hãy dùng vòng lặp ngoài để lấy lần lượt từng đơn hàng.

② Hãy chuẩn bị biến tạm tính cho từng đơn hàng sao cho nó bắt đầu lại từ 0 mỗi khi sang đơn hàng khác.

③ Hãy dùng vòng lặp trong để lấy lần lượt từng giá của đơn hàng đó và cộng vào tạm tính.

④ Sau vòng lặp trong, hãy in một dòng theo dạng "A-1001: 2 món / 1760 yên".

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 1Khi lặp qua orderIds.entries() bằng for...of, index ở vòng đầu tiên là gì?

Câu 2Khi lặp qua một mảng bằng for...in, biến của vòng lặp chứa gì?

Câu 3Nếu dùng vòng lặp lồng nhau để lặp qua một mảng 3 phần tử và một mảng 4 phần tử, thân vòng lặp trong chạy bao nhiêu lần?