for...in和for...of有什么区别

24 阅读3分钟

这两个循环语句在 JavaScript 中有完全不同的用途和行为:

1. 基本概念

for...in

// 遍历对象的可枚举属性(包括继承的)
for (const key in object) {
  console.log(key); // 输出属性名
}

for...of

// 遍历可迭代对象的值
for (const value of iterable) {
  console.log(value); // 输出值
}

2. 主要区别对比

特性for...infor...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);
}