Menu

for...of và for...in trong JavaScript: duyệt mảng và object

for...of và for...in trong JavaScript thực sự khác nhau thế nào: giá trị hay khóa, mảng hay object, và khi nào nên dùng vòng lặp nào.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Hai vòng lặp, hai nhiệm vụ khác nhau

JavaScript có hai dạng vòng lặp (loop) trông gần như giống hệt nhau nhưng làm những việc rất khác nhau. for...of đi qua các giá trị của một iterable. for...in đi qua các khóa của một object. Chỉ khác một chữ, nhưng hành vi khác nhau hoàn toàn.

Toàn bộ câu chuyện nằm trong một ví dụ:

Vòng lặp đầu in ra apple, banana, cherry, tức là các giá trị. Vòng lặp thứ hai in ra 0, 1, 2, tức là các khóa, ở dạng chuỗi. Chọn nhầm một cái là bạn sẽ mất mười phút tự hỏi vì sao mảng của mình toàn là số.

for...of: lấy giá trị từ mọi thứ iterable

for...of là vòng lặp bạn sẽ dùng thường xuyên nhất. Nó chạy được trên mọi thứ JavaScript coi là iterable: mảng, chuỗi, Map, Set, NodeList, generator và nhiều thứ khác.

Không phải xoay xở với chỉ số, không cần scores[i]. Bạn hỏi từng giá trị, bạn nhận từng giá trị. Chuỗi cũng là iterable, nên for...of duyệt qua từng ký tự một:

Cách này cũng xử lý đúng với hầu hết code point Unicode, một điểm cộng nhỏ nhưng có thật so với việc truy cập chuỗi bằng chỉ số.

Lấy chỉ số với for...of

Thứ duy nhất for...of không đưa thẳng cho bạn là vị trí. Khi cần, hãy kết hợp vòng lặp với entries():

names.entries() sinh ra các cặp [index, value], và phép destructuring ở bên trái tách chúng thành hai biến. Cách này thường gọn hơn so với quay về vòng lặp for (let i = 0; ...) cổ điển chỉ vì bạn cần i.

for...in: các khóa của một object

for...in được làm ra cho object thường. Nó duyệt qua các khóa dạng chuỗi có thể liệt kê (enumerable):

Lưu ý rằng bạn nhận được khóa chứ không phải giá trị. Để đọc giá trị, bạn truy cập lại object bằng user[key]. Mọi khóa đều là chuỗi, kể cả khi trông giống số.

for...in còn đi ngược lên chuỗi prototype, nên có thể làm lộ ra các thuộc tính kế thừa. Với object literal do bạn tự viết thì hiếm khi thành vấn đề, nhưng khi lặp qua instance của một lớp hoặc object đến từ thư viện, bạn nên cẩn thận:

Object.hasOwn(user, key) bỏ qua mọi thứ được kế thừa. Trong code hiện đại, phần lớn mọi người tránh hẳn for...in và dùng Object.keys, Object.values hoặc Object.entries, chính là nội dung của phần tiếp theo.

Cách hiện đại để lặp qua object

Thay vì for...in, hãy kết hợp for...of với một trong các hàm hỗ trợ Object.*. Bạn chọn hàm nào trong ba hàm phù hợp với việc mình cần:

Object.entries đặc biệt dễ dùng: destructuring cặp [key, value] đọc tự nhiên như câu nói thường. Và vì các phương thức này chỉ trả về thuộc tính enumerable của chính object đó, bạn không phải lo về những thuộc tính kế thừa ngoài ý muốn.

Đừng dùng for...in với mảng

Về kỹ thuật thì hợp lệ, nhưng rất dễ khiến người ta vấp:

Bạn nhận được 0, 1, 2, và cả tag. Bất kỳ thuộc tính nào ai đó gắn vào mảng, hoặc vào Array.prototype qua một polyfill, cũng sẽ xuất hiện. Các khóa là chuỗi, nên key + 1 sẽ nối chuỗi thay vì cộng số. Và thứ tự lặp không được đảm bảo khớp với thứ tự mảng trong mọi trường hợp đặc biệt.

Quy tắc dễ nhớ:

  • Giá trị của mảng? for...of arr.
  • Chỉ số và giá trị của mảng? for...of arr.entries().
  • Chỉ cần chỉ số để đếm? Vòng lặp cổ điển for (let i = 0; i < arr.length; i++).
  • Khóa hoặc cặp khóa và giá trị của object? Object.keys(obj) / Object.entries(obj) cùng với for...of.

Tóm lại: for...in là công cụ cho trường hợp đặc biệt. for...of cộng với các hàm hỗ trợ của Object đã đủ cho mọi thứ bạn cần.

break và continue dùng được ở cả hai

Cả hai vòng lặp đều hỗ trợ các cách thoát sớm quen thuộc:

continue nhảy sang lần lặp tiếp theo; break thoát hẳn khỏi vòng lặp. Đây là lý do chính để ưu tiên for...of hơn .forEach(): callback của forEach không thể break ra khỏi vòng lặp, còn for...of thì có.

Tóm tắt đặt cạnh nhau

Bốn vòng lặp, bốn nhiệm vụ, một cách nghĩ: nếu bạn muốn lấy giá trị từ thứ gì đó iterable, dùng for...of. Nếu bạn muốn làm việc với thuộc tính của một object, đi qua Object.keys / Object.values / Object.entries, cũng với for...of. Hãy để dành for...in cho trường hợp hiếm khi bạn thật sự muốn mọi khóa chuỗi enumerable của một object, dù kế thừa hay không.

Tiếp theo: truthy và falsy

Mọi vòng lặp và câu lệnh if trong JavaScript rốt cuộc đều hỏi cùng một câu: giá trị này có được coi là true không? Câu trả lời không phải lúc nào cũng hiển nhiên: chuỗi rỗng, số 0, null và undefined đều có thể hành xử khác với điều bạn nghĩ. Giá trị truthy và falsy là chủ đề tiếp theo.

Câu hỏi thường gặp

for...of và for...in trong JavaScript khác nhau thế nào?

for...of duyệt qua các giá trị của một iterable như mảng, chuỗi, Map hay Set. for...in duyệt qua các khóa (tên thuộc tính) của một object. Với mảng ['a', 'b'], for...of cho bạn 'a' và 'b'; còn for...in cho bạn '0' và '1' ở dạng chuỗi.

Có dùng for...of với object được không?

Không dùng trực tiếp được, vì object thường không phải iterable. Hãy dùng Object.keys(obj), Object.values(obj) hoặc Object.entries(obj) để lấy một mảng rồi duyệt bằng for...of. Cách viết phổ biến là for (const [key, value] of Object.entries(obj)).

Vì sao không nên dùng for...in với mảng?

Về kỹ thuật thì vẫn chạy, nhưng for...in đi qua mọi thuộc tính enumerable, kể cả thuộc tính kế thừa và bất cứ thứ gì ai đó thêm vào Array.prototype. Nó còn trả về khóa ở dạng chuỗi, và thứ tự không phải lúc nào cũng được đảm bảo với các khóa trông giống số nguyên. Hãy dùng for...of để lấy giá trị, hoặc vòng lặp for cổ điển khi bạn cần chỉ số.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu