Câu 1Khi lặp qua orderIds.entries() bằng for...of, index ở vòng đầu tiên là gì?
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
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òng | Giá trị của i và điều kiện | Điều xảy ra trong thân |
|---|---|---|
| Vòng 1 | i = 0 / 0 < 3 là true | Món 1: Cốc sứ |
| Vòng 2 | i = 1 / 1 < 3 là true | Món 2: Sổ tay |
| Vòng 3 | i = 2 / 2 < 3 là true | Món 3: Bút bi |
| Đầu vòng 4 | i = 3 / 3 < 3 là false | Thâ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.
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ấy | Giá trị trong biến | Dòng được in ra |
|---|---|---|
| Lấy phần tử bằng of | Chí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ến | Cặ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.
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ố
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áp | Giá trị trong biến | Phù 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ỗi | Bạ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.
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
store— đổi từ "Shibuya" sang "Yokohama" ở mỗi vòng- Thân vòng ngoài chỉ chạy 2 lần
product— đổi từ "Cốc sứ" sang "Sổ tay" ở mỗi vòng trong- Đọc được trực tiếp
storecủa vòng ngoài - Riêng thân này chạy 2 × 2 = 4 lần
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.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
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?