Cách sử dụng vòng lặp for...in trong JavaScript

Javascript căn bản | by Học Javascript

Trong JavaScript, vòng lặp đóng vai trò quan trọng trong việc xử lý dữ liệu và thực thi các tác vụ lặp đi lặp lại một cách hiệu quả. Trong số đó, vòng lặp for...in là một công cụ hữu ích giúp duyệt qua các thuộc tính của đối tượng (Object) và chỉ mục của mảng (Array). Mặc dù cú pháp đơn giản, nhưng nếu không sử dụng đúng cách, vòng lặp này có thể gây ra những lỗi không mong muốn. Trong bài viết này, mình sẽ cùng tìm hiểu cách sử dụng vòng lặp for...in, ưu nhược điểm của nó, cũng như các trường hợp nên và không nên áp dụng.

Giới thiệu về vòng lặp for...in trong Javascript

Vòng lặp for...in trong JavaScript là một loại vòng lặp đặc biệt được sử dụng để duyệt qua các thuộc tính (keys) của một đối tượng (Object) hoặc chỉ mục (index) của một mảng (Array).

Cú pháp chung:

for (let key in object) {
    // Thực hiện thao tác với từng key của object
}

Trong đó:

  • key là tên thuộc tính hoặc chỉ mục của đối tượng/mảng.
  • object là đối tượng hoặc mảng cần duyệt qua.

Ví dụ đơn giản về for...in với một Object:

const person = { name: "John", age: 30, city: "New York" };

for (let key in person) {
    console.log(key + ": " + person[key]);
}

Kết quả:

name: John  
age: 30  
city: New York  

Ở đây, vòng lặp for...in duyệt qua từng thuộc tính (name, age, city) của đối tượng person và in ra giá trị tương ứng.

Khi nào nên sử dụng for...in?

Vòng lặp for...in thường được sử dụng trong các trường hợp sau:

  • Duyệt qua các thuộc tính của một Object để lấy danh sách thuộc tính hoặc thao tác trên từng giá trị.
  • Duyệt qua chỉ mục của một Array, mặc dù cách này ít được khuyến khích vì for...in không đảm bảo thứ tự các phần tử trong mảng.
  • Duyệt qua thuộc tính của một đối tượng tùy chỉnh (custom object), đặc biệt khi các thuộc tính không có thứ tự cố định.

Ví dụ sử dụng for...in để duyệt Object

const student = {
    id: 101,
    name: "Alice",
    grade: "A"
};

for (let prop in student) {
    console.log(`Thuộc tính: ${prop}, Giá trị: ${student[prop]}`);
}

Kết quả:

Thuộc tính: id, Giá trị: 101  
Thuộc tính: name, Giá trị: Alice  
Thuộc tính: grade, Giá trị: A  

Lưu ý: Nếu một Object có kế thừa từ prototype, vòng lặp for...in cũng sẽ duyệt qua các thuộc tính được kế thừa. Điều này có thể gây lỗi ngoài ý muốn nếu không kiểm tra với hasOwnProperty().

So sánh for...in với các vòng lặp khác

Loại vòng lặp Duyệt Object Duyệt Array Duyệt theo thứ tự Thích hợp khi nào?
for...in Có nhưng không khuyến khích Không đảm bảo thứ tự Khi cần duyệt thuộc tính của Object
for Không Đảm bảo thứ tự Khi cần duyệt qua Array theo index
forEach() Không Đảm bảo thứ tự Khi cần thao tác với từng phần tử của mảng
for...of Không Đảm bảo thứ tự Khi cần lặp qua giá trị của Array hoặc iterable

Ví dụ so sánh for...infor...of khi duyệt một mảng:

const numbers = [10, 20, 30];

console.log("Dùng for...in:");
for (let index in numbers) {
    console.log(index); // Chỉ in ra index (0, 1, 2)
}

console.log("Dùng for...of:");
for (let value of numbers) {
    console.log(value); // In ra giá trị (10, 20, 30)
}

Kết quả:

  • for...in duyệt qua chỉ mục (index) của mảng.
  • for...of duyệt qua giá trị của mảng, giúp mã nguồn dễ đọc hơn khi làm việc với mảng.

Cú pháp vòng lặp for...in trong Javascript

Vòng lặp for...in trong JavaScript được sử dụng để lặp qua tất cả các thuộc tính có thể liệt kê (enumerable properties) của một đối tượng. Nó thường được sử dụng với Object, nhưng cũng có thể áp dụng với Array. Tuy nhiên, khi dùng với mảng, for...in không phải lúc nào cũng đảm bảo thứ tự của các phần tử.

Cấu trúc chung

Cú pháp của vòng lặp for...in như sau:

for (let key in object) {
    // Thao tác với từng key của object
}

Giải thích:

  • key: Là biến sẽ chứa tên của thuộc tính (hoặc chỉ mục nếu dùng với mảng).
  • object: Là đối tượng hoặc mảng cần lặp qua.
  • Mỗi lần lặp, biến key sẽ lấy giá trị là một tên thuộc tính của đối tượng.
  • Bên trong vòng lặp, bạn có thể sử dụng object[key] để truy cập giá trị của thuộc tính đó.

Ví dụ đơn giản với Object

const person = { name: "John", age: 30, city: "New York" };

for (let key in person) {
    console.log(key + ": " + person[key]);
}

Kết quả:

name: John  
age: 30  
city: New York  

Giải thích:

  • Vòng lặp for...in sẽ duyệt qua từng thuộc tính của đối tượng person.
  • Trong mỗi lần lặp, key sẽ lần lượt nhận các giá trị "name", "age", "city".
  • person[key] sẽ truy xuất giá trị của từng thuộc tính tương ứng.

Ví dụ với Object có nhiều thuộc tính

const student = {
    id: 102,
    name: "Alice",
    age: 22,
    major: "Computer Science"
};

for (let property in student) {
    console.log(`Thuộc tính: ${property}, Giá trị: ${student[property]}`);
}

Kết quả:

Sử dụng for...in với Array

Mặc dù for...in có thể được dùng để lặp qua một mảng, nhưng nó duyệt theo chỉ mục (index) thay vì giá trị, và không đảm bảo thứ tự các phần tử trong một số trường hợp.

const colors = ["red", "green", "blue"];

for (let index in colors) {
    console.log(index + ": " + colors[index]);
}
Kết quả:
0: red  
1: green  
2: blue  

Lưu ý: Dù có thể dùng for...in để duyệt mảng, nhưng for...of là lựa chọn tốt hơn nếu bạn muốn lấy trực tiếp giá trị của mảng.

Kiểm tra thuộc tính có phải của Object hay không

Khi sử dụng for...in với Object, nếu Object được kế thừa từ một prototype, nó cũng sẽ lặp qua cả các thuộc tính kế thừa. Để tránh điều này, bạn có thể dùng phương thức hasOwnProperty() để kiểm tra xem thuộc tính có thực sự thuộc về Object hay không.

Ví dụ:

const car = { brand: "Toyota", model: "Camry", year: 2022 };

Object.prototype.color = "black"; // Thêm thuộc tính vào prototype

for (let key in car) {
    if (car.hasOwnProperty(key)) {
        console.log(key + ": " + car[key]);
    }
}

Kết quả:

brand: Toyota  
model: Camry  
year: 2022  

Lưu ý: color là thuộc tính kế thừa từ Object.prototype, nhưng vì ta kiểm tra bằng hasOwnProperty(), nó không bị in ra.

Không nên dùng for...in với mảng nếu không cần thiết

for...in lặp qua chỉ mục của mảng dưới dạng chuỗi, nó có thể gây ra lỗi trong một số trường hợp, đặc biệt khi có thuộc tính tùy chỉnh trên mảng.

Ví dụ lỗi khi dùng for...in với mảng

const numbers = [10, 20, 30];
numbers.customProperty = "Test";

for (let index in numbers) {
    console.log(index + ": " + numbers[index]);
}
Kết quả:
0: 10  
1: 20  
2: 30  
customProperty: undefined  

Lỗi: Vì customProperty cũng được lặp qua nhưng nó không có giá trị tương ứng trong mảng.

Cách đúng: Dùng for...of để lặp qua giá trị của mảng.

for (let value of numbers) {
    console.log(value);
}

Sử dụng for...in với Object trong Javascript

Trong JavaScript, vòng lặp for...in chủ yếu được sử dụng để duyệt qua các thuộc tính của một Object. Khi làm việc với đối tượng, vòng lặp này giúp lấy danh sách các key (tên thuộc tính) và truy cập giá trị của chúng một cách dễ dàng.

Duyệt qua các thuộc tính của Object

Vòng lặp for...in cho phép duyệt qua tất cả các thuộc tính có thể liệt kê (enumerable properties) của một Object, bao gồm cả những thuộc tính được thêm động trong quá trình thực thi.

Cấu trúc chung:

for (let key in object) {
    console.log(key + ": " + object[key]);
}

Trong đó:

  • key là tên của từng thuộc tính trong Object.
  • object[key] truy xuất giá trị của thuộc tính tương ứng.

Ví dụ minh họa

Ví dụ 1: Duyệt qua thuộc tính của một Object đơn giản

const student = {
    id: 101,
    name: "Alice",
    age: 20,
    major: "Computer Science"
};

for (let key in student) {
    console.log(`${key}: ${student[key]}`);
}

Kết quả:

Giải thích:

  • Vòng lặp for...in duyệt qua từng thuộc tính của Object student và in ra tên thuộc tính cùng giá trị của nó.
  • Ở mỗi vòng lặp, key nhận giá trị "id", "name", "age", "major", và student[key] lấy giá trị tương ứng.

Ví dụ 2: Duyệt qua Object có chứa Object lồng nhau

const company = {
    name: "Tech Corp",
    location: "New York",
    employees: {
        manager: "John",
        developer: "Alice",
        designer: "Bob"
    }
};

for (let key in company) {
    console.log(`${key}: ${company[key]}`);

    if (typeof company[key] === "object") {
        for (let subKey in company[key]) {
            console.log(`   ${subKey}: ${company[key][subKey]}`);
        }
    }
}

Kết quả:

Giải thích:

  • Khi gặp thuộc tính employees có kiểu dữ liệu là Object, ta sử dụng vòng lặp for...in lồng nhau để duyệt tiếp các thuộc tính bên trong.
  • Kết quả hiển thị dạng cây phân cấp giúp dễ đọc dữ liệu.

Lưu ý khi dùng for...in với Object trong Javascript

for...in duyệt cả thuộc tính kế thừa từ prototype

Khi một Object được tạo dựa trên một prototype, for...in cũng sẽ lặp qua các thuộc tính kế thừa từ prototype. Điều này có thể gây ra lỗi ngoài ý muốn nếu không xử lý đúng cách.

Ví dụ lỗi kế thừa từ prototype

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.country = "USA"; // Thêm thuộc tính vào prototype

const person1 = new Person("John", 30);

for (let key in person1) {
    console.log(key + ": " + person1[key]);
}

Kết quả:

name: John  
age: 30  
country: USA   (thuộc tính từ prototype cũng bị duyệt qua)

Giải thích:

  • nameage là thuộc tính riêng của person1.
  • Nhưng country là thuộc tính được kế thừa từ prototype, và nó cũng bị duyệt bởi for...in.
  • Điều này có thể gây lỗi nếu không mong muốn lấy thuộc tính kế thừa.

Cách tránh lỗi: Dùng hasOwnProperty() để kiểm tra thuộc tính chỉ thuộc về Object đó.

for (let key in person1) {
    if (person1.hasOwnProperty(key)) {
        console.log(key + ": " + person1[key]);
    }
}

Kết quả đúng:

name: John  
age: 30  

Không đảm bảo thứ tự các thuộc tính

Không giống như mảng, for...in không đảm bảo thứ tự khi duyệt các thuộc tính của Object.

Ví dụ minh họa thứ tự không đảm bảo

const data = {
    b: "Banana",
    c: "Cherry",
    a: "Apple"
};

for (let key in data) {
    console.log(`${key}: ${data[key]}`);
}
Kết quả có thể khác nhau:
b: Banana  
c: Cherry  
a: Apple  
hoặc
a: Apple  
b: Banana  
c: Cherry  

Lưu ý:

  • Thứ tự thuộc tính không được đảm bảo khi dùng for...in.
  • Nếu cần duyệt thuộc tính theo thứ tự cụ thể, hãy dùng Object.keys() kết hợp với sort().

Cách duyệt thuộc tính theo thứ tự tăng dần

const sortedKeys = Object.keys(data).sort();
for (let key of sortedKeys) {
    console.log(`${key}: ${data[key]}`);
}

Kết quả luôn đúng:

a: Apple  
b: Banana  
c: Cherry  

Sử dụng for...in với Array trong Javascript

Trong JavaScript, vòng lặp for...in có thể được sử dụng để duyệt qua chỉ mục (index) của một mảng. Tuy nhiên, đây không phải là cách tối ưu để duyệt mảng, và trong một số trường hợp, có thể dẫn đến kết quả không mong muốn.

Duyệt qua các chỉ mục của mảng

Vòng lặp for...in khi sử dụng với mảng sẽ lặp qua các chỉ mục (index) của mảng thay vì các giá trị trực tiếp.

Ví dụ 1: Duyệt qua mảng bằng for...in

const numbers = [10, 20, 30];

for (let index in numbers) {
    console.log(index + ": " + numbers[index]);
}

Kết quả:

0: 10  
1: 20  
2: 30  

Giải thích:

  • index lần lượt nhận các giá trị "0", "1", "2" (là chỉ mục của mảng).
  • numbers[index] lấy giá trị tương ứng tại mỗi chỉ mục.

So sánh for...in với for...of khi lặp qua mảng

Mặc dù for...in có thể được sử dụng với mảng, nhưng nó không phải là cách tốt nhất để duyệt qua các phần tử. Trong hầu hết các trường hợp, bạn nên sử dụng for...of thay vì for...in.

Đặc điểm for...in for...of
Duyệt qua Chỉ mục (index) Giá trị của phần tử
Đối tượng áp dụng Object, Array Iterable (Array, String, Set, Map, NodeList, v.v.)
Thứ tự duyệt Không đảm bảo Đảm bảo theo thứ tự phần tử
Duyệt qua thuộc tính không mong muốn Có thể (nếu thuộc tính thêm vào prototype) Không

Ví dụ 2: Duyệt mảng bằng for...of (Tốt hơn for...in)

const numbers = [10, 20, 30];

for (let value of numbers) {
    console.log(value);
}
Kết quả:
10  
20  
30  

Lợi ích của for...of:

  • Trực tiếp lấy giá trị của phần tử, không cần truy xuất array[index].
  • Không gặp vấn đề nếu mảng có thêm thuộc tính trong prototype.

Lưu ý khi dùng for...in với mảng trong Javascript

for...in có thể lặp qua cả thuộc tính mở rộng của mảng

Mảng trong JavaScript là Object, vì vậy nếu bạn thêm thuộc tính cho một mảng, vòng lặp for...in sẽ duyệt qua cả các thuộc tính này.

Ví dụ: Mảng có thuộc tính mở rộng

const numbers = [10, 20, 30];
numbers.customProperty = "Extra Data"; // Thêm thuộc tính vào mảng

for (let index in numbers) {
    console.log(index + ": " + numbers[index]);
}

Kết quả:

0: 10  
1: 20  
2: 30  
customProperty: Extra Data   (Không mong muốn)

Cách tránh lỗi này: Dùng hasOwnProperty() để kiểm tra chỉ mục thực sự của mảng.

for (let index in numbers) {
    if (numbers.hasOwnProperty(index)) {
        console.log(index + ": " + numbers[index]);
    }
}

Hoặc tốt hơn, dùng for...of thay vì for...in.

for...in không đảm bảo thứ tự

Mặc dù trong thực tế, hầu hết các trình duyệt duyệt mảng theo thứ tự tăng dần, nhưng JavaScript không đảm bảo thứ tự này với for...in. Điều này có thể gây lỗi nếu bạn kỳ vọng mảng luôn được duyệt theo thứ tự ban đầu.

Cách duyệt đúng thứ tự: Sử dụng vòng lặp for, forEach(), hoặc for...of.

const numbers = [10, 20, 30];

// Cách 1: Dùng for
for (let i = 0; i < numbers.length; i++) {
    console.log(numbers[i]);
}

// Cách 2: Dùng forEach()
numbers.forEach(value => console.log(value));

// Cách 3: Dùng for...of
for (let value of numbers) {
    console.log(value);
}

Mặc dù for...in là một cách hữu ích để duyệt qua các thuộc tính của Object, nhưng nó cũng có một số hạn chế quan trọng mà bạn cần lưu ý, đặc biệt khi sử dụng với mảng.

Không nên sử dụng for...in với mảng nếu không cần duyệt theo chỉ mục

Vấn đề khi dùng for...in với mảng

Như đã đề cập ở phần trước, for...in duyệt qua các chỉ mục của mảng thay vì giá trị. Tuy nhiên, nó không đảm bảo thứ tự của các phần tử trong mảng.

Ví dụ minh họa:

const numbers = [10, 20, 30];

for (let index in numbers) {
    console.log(index + ": " + numbers[index]);
}

Kết quả:

0: 10  
1: 20  
2: 30  

Mặc dù trong hầu hết các trình duyệt, chỉ mục vẫn được duyệt theo thứ tự, nhưng theo chuẩn ECMAScript, thứ tự duyệt của for...in không được đảm bảo. Vì vậy, không nên sử dụng for...in nếu bạn cần duyệt qua mảng theo thứ tự từ đầu đến cuối.

Cách tốt hơn để duyệt qua giá trị của mảng:

  • Dùng for...of để lấy giá trị trực tiếp:
for (let value of numbers) {
    console.log(value);
}
Dùng forEach() nếu không cần chỉ mục:
numbers.forEach(value => console.log(value));
Dùng vòng lặp for nếu cần truy cập chỉ mục và giá trị:
for (let i = 0; i < numbers.length; i++) {
    console.log(i + ": " + numbers[i]);
}

for...in lặp qua cả thuộc tính kế thừa

Trong JavaScript, một Object có thể kế thừa các thuộc tính từ prototype. Khi sử dụng for...in, vòng lặp sẽ duyệt qua cả các thuộc tính kế thừa từ prototype, điều này có thể gây ra kết quả không mong muốn.

Ví dụ minh họa

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.country = "Vietnam"; // Thêm thuộc tính vào prototype

const person = new Person("John", 30);

for (let key in person) {
    console.log(key + ": " + person[key]);
}

Kết quả:

name: John  
age: 30  
country: Vietnam   (Thuộc tính không mong muốn từ prototype)

Vấn đề:

  • country không phải là thuộc tính trực tiếp của person, nhưng nó vẫn xuất hiện khi sử dụng for...in.
  • Nếu Object có nhiều thuộc tính từ prototype, chúng ta sẽ nhận kết quả không mong muốn.

Cách khắc phục: Dùng hasOwnProperty() để kiểm tra chỉ lấy các thuộc tính riêng của Object.

for (let key in person) {
    if (person.hasOwnProperty(key)) {
        console.log(key + ": " + person[key]);
    }
}

Kết quả mong muốn:

name: John  
age: 30  

for...in có thể lặp qua cả thuộc tính không phải số trong mảng

Do JavaScript coi mảng là một loại Object đặc biệt, nếu bạn gán thuộc tính tùy chỉnh vào một mảng, vòng lặp for...in sẽ duyệt qua cả thuộc tính đó.

Ví dụ minh họa:

const numbers = [10, 20, 30];
numbers.customProperty = "Extra Data"; // Thêm thuộc tính vào mảng

for (let index in numbers) {
    console.log(index + ": " + numbers[index]);
}

Kết quả:

0: 10  
1: 20  
2: 30  
customProperty: Extra Data   (Thuộc tính không mong muốn)

Cách khắc phục:

  • Dùng hasOwnProperty() để kiểm tra chỉ mục hợp lệ (ít phổ biến).
  • Sử dụng for...of thay vì for...in để tránh lỗi này.
Hạn chế Vấn đề Cách khắc phục
Duyệt cả thuộc tính kế thừa Lặp qua các thuộc tính từ prototype. Dùng hasOwnProperty() để lọc thuộc tính riêng của Object.
Không đảm bảo thứ tự của mảng Thứ tự duyệt không chắc chắn theo chỉ mục tăng dần. Dùng for, forEach(), hoặc for...of để duyệt mảng.
Duyệt cả thuộc tính mở rộng của mảng Nếu mảng có thuộc tính tùy chỉnh, vòng lặp sẽ lặp qua cả thuộc tính này. Không sử dụng for...in với mảng, hoặc dùng for...of.
Không phù hợp để duyệt qua giá trị của mảng for...in chỉ duyệt qua chỉ mục, không trực tiếp lấy giá trị. Dùng for...of hoặc forEach().

Kết quả

Vòng lặp for...in là một công cụ hữu ích trong JavaScript khi cần duyệt qua các thuộc tính của Object. Nó giúp chúng ta dễ dàng truy cập tên thuộc tính và giá trị tương ứng, đặc biệt khi làm việc với các đối tượng có nhiều key. Tuy nhiên, khi sử dụng for...in, cần lưu ý một số hạn chế quan trọng, đặc biệt là:

  • Không nên dùng for...in để duyệt mảng, vì nó không đảm bảo thứ tự và có thể lặp qua cả thuộc tính tùy chỉnh.
  • for...in cũng duyệt cả các thuộc tính kế thừa từ prototype, nên cần dùng hasOwnProperty() để lọc thuộc tính mong muốn.
  • Khi làm việc với mảng, nên ưu tiên sử dụng for...of, forEach() hoặc vòng lặp for truyền thống để đảm bảo hiệu suất và tính chính xác.

Tóm lại, for...in là một vòng lặp quan trọng trong JavaScript, nhưng nó chỉ nên được sử dụng trong các trường hợp phù hợp. Hiểu rõ ưu và nhược điểm của nó sẽ giúp bạn viết mã JavaScript hiệu quả, tối ưu hơn.

Bài viết liên quan

  • 2