这两个循环语句在 JavaScript 中有完全不同的用途和行为:
1. 基本概念
for...in
// 遍历对象的可枚举属性(包括继承的)
for (const key in object) {
console.log(key); // 输出属性名
}
for...of
// 遍历可迭代对象的值
for (const value of iterable) {
console.log(value); // 输出值
}
2. 主要区别对比
| 特性 | for...in | for...of |
|---|---|---|
| 遍历内容 | 对象的键名 (属性名) | 可迭代对象的值 |
| 适用对象 | 对象 (包括数组,但不推荐) | 可迭代对象 (Array, String, Map, Set等) |
| 原型链 | 会遍历原型链上的可枚举属性 | 只遍历当前对象,不遍历原型链 |
| 顺序 | 不保证顺序 (ES6后对普通对象有一定顺序) | 按照迭代器定义的顺序 |
| 性能 | 较慢,因为要检查原型链 | 较快,直接获取值 |
3. 使用示例对比
数组遍历
const arr = ['a', 'b', 'c'];
// for...in (不推荐用于数组)
for (const index in arr) {
console.log(index); // 输出: '0', '1', '2' (字符串)
console.log(typeof index); // 'string'
}
// for...of (推荐)
for (const value of arr) {
console.log(value); // 输出: 'a', 'b', 'c'
}
对象遍历
const obj = { a: 1, b: 2, c: 3 };
// for...in (适用)
for (const key in obj) {
console.log(key, obj[key]); // 输出: 'a' 1, 'b' 2, 'c' 3
}
// for...of (默认不可用,对象不可迭代)
// for (const value of obj) { } // TypeError: obj is not iterable
// 需要手动获取可迭代的条目
for (const [key, value] of Object.entries(obj)) {
console.log(key, value); // 输出: 'a' 1, 'b' 2, 'c' 3
}
4. 详细示例
字符串遍历
const str = "hello";
// for...in
for (const index in str) {
console.log(index); // 0, 1, 2, 3, 4 (索引)
}
// for...of
for (const char of str) {
console.log(char); // 'h', 'e', 'l', 'l', 'o'
}
Map 和 Set 遍历
// Map
const map = new Map([['a', 1], ['b', 2]]);
// for...in (不适用,Map没有可枚举属性)
for (const key in map) {
console.log(key); // 不会执行
}
// for...of
for (const [key, value] of map) {
console.log(key, value); // 'a' 1, 'b' 2
}
// Set
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value); // 1, 2, 3
}
原型链影响
function Person() {
this.name = 'John';
}
Person.prototype.age = 30;
const person = new Person();
// for...in 会遍历原型链属性
for (const key in person) {
console.log(key); // 输出: 'name', 'age'
}
// 使用 hasOwnProperty 过滤
for (const key in person) {
if (person.hasOwnProperty(key)) {
console.log(key); // 只输出: 'name'
}
}
5. 哪些数据结构支持 for...of
支持 Symbol.iterator 方法的对象都可以使用 for...of:
// 1. 数组
const arr = [1, 2, 3];
// 2. 字符串
const str = "abc";
// 3. Map
const map = new Map([['a', 1]]);
// 4. Set
const set = new Set([1, 2]);
// 5. NodeList (DOM元素集合)
const nodeList = document.querySelectorAll('div');
// 6. Arguments 对象
function test() {
for (const arg of arguments) {
console.log(arg);
}
}
// 7. TypedArray
const typedArray = new Uint8Array([1, 2, 3]);
// 8. 自定义可迭代对象
const myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
6. 如何让普通对象支持 for...of
// 方法1: 实现 Symbol.iterator
const obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function* () {
for (const key of Object.keys(this)) {
yield this[key];
}
}
};
for (const value of obj) {
console.log(value); // 1, 2, 3
}
// 方法2: 使用 Object.values() 等辅助方法
for (const value of Object.values(obj)) {
console.log(value); // 1, 2, 3
}
7. 性能对比
const arr = new Array(1000000).fill(0);
// for...of (最快的方式之一)
console.time('for...of');
for (const value of arr) {
// 遍历
}
console.timeEnd('for...of');
// for...in (最慢)
console.time('for...in');
for (const index in arr) {
const value = arr[index];
}
console.timeEnd('for...in');
// 传统 for 循环 (最快)
console.time('for');
for (let i = 0; i < arr.length; i++) {
const value = arr[i];
}
console.timeEnd('for');
总结
- 使用 for...in 遍历对象的属性名,注意它会遍历原型链,常用于调试或操作对象属性
- 使用 for...of 遍历可迭代对象的值,代码更简洁,性能更好,是现代JavaScript推荐的方式
- 数组遍历:优先使用
for...of,避免使用for...in - 对象遍历:使用
for...in或Object.keys/values/entries+for...of
// 最佳实践总结
const obj = { a: 1, b: 2 };
const arr = [1, 2, 3];
// 遍历对象属性名
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
// 或使用 Object.entries + for...of
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// 遍历数组值
for (const value of arr) {
console.log(value);
}