for...in 与 for...of

7 阅读7分钟

for...in 和 for...of 是 JavaScript 中两种常用的遍历语法,核心差异体现在遍历目标、遍历内容、适用场景上,且 for...of 是 ES6 新增特性,解决了 for...in 的诸多痛点,以下从核心区别、详细特性、使用场景、常见坑点四个维度全面解析,附实用示例。

一、核心核心区别(一句话总结)

特性for...infor...of
遍历核心遍历键名 / 属性名(字符串类型)遍历键值 / 元素值(原始值类型)
遍历目标可枚举的对象属性(含原型链)实现 ** 可迭代协议(iterable)** 的对象
适用类型普通对象、数组(不推荐)、字符串数组、字符串、Map、Set、NodeList、Generator 等
原型链属性会遍历自身 + 原型链的可枚举属性仅遍历自身可迭代元素,无视原型链
数组索引索引为字符串(如 "0"、"1")索引为数字(遍历的是值,可通过 Array.prototype.entries () 获取索引)
break/continue支持支持
return 终止仅在函数内可终止遍历仅在函数内可终止遍历

二、for...in 详细特性与使用

1. 核心作用

专门用于遍历对象的可枚举属性,包括自身属性原型链上的继承属性(可通过 hasOwnProperty 过滤原型链属性),遍历的属性名始终为字符串类型

2. 支持的遍历目标

  • 普通 JavaScript 对象({})
  • 数组(不推荐使用,易出问题)
  • 字符串(遍历字符索引,字符串类型)

3. 基础示例

(1)遍历普通对象(核心适用场景)

javascript

运行

const person = { name: "张三", age: 20, gender: "男" };
// 遍历对象的属性名
for (const key in person) {
  console.log("键名:", key, ",类型:", typeof key); // 键名:name,类型:string
  console.log("键值:", person[key]); // 键值:张三
}

(2)过滤原型链属性(必做操作)

for...in 会遍历原型链上的可枚举属性,需通过 obj.hasOwnProperty(key) 确保只遍历自身属性

javascript

运行

// 给 Object 原型添加一个可枚举属性
Object.prototype.sayHello = function() {};
const person = { name: "张三", age: 20 };

// 未过滤原型链:会遍历到 sayHello
for (const key in person) {
  console.log(key); // name、age、sayHello
}

// 过滤原型链:仅遍历自身属性(推荐写法)
for (const key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key); // name、age(正确)
  }
}

(3)遍历数组(不推荐)

遍历数组时,得到的索引是字符串类型,且若数组有自定义属性 / 原型链属性,会被一并遍历:

javascript

运行

const arr = [10, 20, 30];
arr.customProp = "自定义属性"; // 给数组添加自定义属性

for (const index in arr) {
  console.log("索引:", index, ",类型:", typeof index); // 0(string)、1(string)、2(string)、customProp(string)
  console.log("值:", arr[index]); // 10、20、30、自定义属性
}

三、for...of 详细特性与使用

1. 核心作用

ES6 新增,专门用于遍历实现了可迭代协议(iterable) 的对象,直接遍历元素值,无视原型链属性,是遍历数组、类数组、集合等的首选方案

2. 可迭代协议(iterable)

一个对象只要实现了 [Symbol.iterator] 方法,就是可迭代对象,for...of 会调用该方法生成迭代器,依次遍历迭代器的返回值。JS 中内置的可迭代对象(无需手动实现):

  • 数组(Array)、字符串(String)
  • 集合(Set)、映射(Map)
  • 类数组对象:NodeList(DOM 节点集合)、Arguments(函数参数集合)
  • Generator 对象、TypedArray 等

3. 基础示例

(1)遍历数组(核心适用场景)

直接遍历数组元素值,无需通过索引取值,索引为数字类型(如需索引可结合 entries()):

javascript

运行

const arr = [10, 20, 30];
arr.customProp = "自定义属性"; // 自定义属性不会被遍历

// 仅遍历元素值(推荐)
for (const value of arr) {
  console.log("值:", value); // 10、20、30(无视自定义属性)
}

// 遍历「索引+值」(通过 entries())
for (const [index, value] of arr.entries()) {
  console.log("索引:", index, ",值:", value); // 0:10、1:20、2:30(index 是数字)
}

(2)遍历字符串

直接遍历字符值,而非索引,比 for...in 更直观:

javascript

运行

const str = "abc";
// for...of 遍历字符值
for (const char of str) {
  console.log(char); // a、b、c
}
// for...in 遍历索引(字符串类型)
for (const index in str) {
  console.log(index, str[index]); // 0 a、1 b、2 c
}

(3)遍历 Set/Map(天然支持)

  • Set 遍历元素值(无重复)
  • Map 遍历 **[key, value] 数组 **,可直接解构

javascript

运行

// 遍历 Set
const set = new Set([1, 2, 2, 3]);
for (const value of set) {
  console.log(value); // 1、2、3(自动去重)
}

// 遍历 Map
const map = new Map([["name", "张三"], ["age", 20]]);
for (const [key, value] of map) {
  console.log(key, value); // name 张三、age 20
}

(4)遍历 DOM 节点集合(NodeList)

前端开发常用,直接遍历DOM 节点,无需转换为数组:

javascript

运行

// 获取所有按钮节点(NodeList 是可迭代对象)
const buttons = document.querySelectorAll("button");
for (const btn of buttons) {
  // 给每个按钮绑定点击事件
  btn.addEventListener("click", () => {
    console.log("按钮被点击");
  });
}

(5)遍历 Arguments(函数参数)

javascript

运行

function fn() {
  for (const arg of arguments) {
    console.log(arg); // 1、2、3
  }
}
fn(1, 2, 3);

4. 关键特性:无法遍历普通对象

重要:for...of 不能直接遍历普通 JavaScript 对象({}) ,因为普通对象未实现可迭代协议,强行遍历会报错:

javascript

运行

const person = { name: "张三", age: 20 };
// 报错:person is not iterable
for (const value of person) {
  console.log(value);
}

解决方法:若需用 for...of 遍历对象,可通过 Object.keys/values/entries 转换为可迭代数组:

javascript

运行

const person = { name: "张三", age: 20 };
// 遍历对象的值
for (const value of Object.values(person)) {
  console.log(value); // 张三、20
}
// 遍历对象的「键+值」
for (const [key, value] of Object.entries(person)) {
  console.log(key, value); // name 张三、age 20
}

四、核心使用场景(官方推荐)

1. 优先用 for...in 的场景

仅一个:遍历普通对象的自身可枚举属性,且必须配合 hasOwnProperty 过滤原型链。

javascript

运行

// 唯一推荐场景:遍历普通对象
const obj = { a: 1, b: 2 };
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(`${key}: ${obj[key]}`);
  }
}

2. 优先用 for...of 的场景

所有可迭代对象的遍历,这是 for...of 的设计初衷,包括:

  • 数组遍历(替代 for 循环 /for...in,最常用)
  • 字符串、Set、Map 遍历
  • DOM 节点集合(NodeList)、Arguments 类数组遍历
  • 异步遍历(可结合 for await...of 遍历异步可迭代对象,如接口请求结果)

3. 绝对避免的场景

  • 用 for...in 遍历数组(易遍历到自定义属性 / 原型链属性,索引为字符串)
  • 用 for...of 直接遍历普通对象(会报错,需先转换)

五、常见坑点与避坑指南

1. for...in 遍历数组的坑

  • 坑 1:索引是字符串类型,若用于数字计算会出问题;
  • 坑 2:会遍历数组的自定义属性和原型链属性
  • 坑 3:遍历顺序不保证严格按索引排序(对稀疏数组可能异常)。

javascript

运行

const arr = [1, 2];
arr[10] = 10; // 稀疏数组
arr.prop = "test";

// 坑:遍历顺序不是 0、1、10,且包含 prop
for (const index in arr) {
  console.log(index); // 0、1、10、prop
}

2. for...in 忘记过滤原型链的坑

若未用 hasOwnProperty,会遍历到 Object 原型或自定义原型的可枚举属性,导致数据污染:

javascript

运行

// 自定义原型
function Person() {}
Person.prototype.age = 20;
const p = new Person();
p.name = "张三";

// 未过滤原型:遍历到 name 和 age
for (const key in p) {
  console.log(key); // name、age
}

3. for...of 遍历普通对象的坑

直接遍历普通对象会报错,需通过 Object.keys/values/entries 转换为数组后再遍历:

javascript

运行

// 错误写法
const obj = { a: 1 };
for (const v of obj) {} // Uncaught TypeError: obj is not iterable

// 正确写法
for (const v of Object.values(obj)) {
  console.log(v); // 1
}

六、补充:for await...of(异步遍历)

for...of 的异步版本,用于遍历异步可迭代对象(如返回 Promise 的 Generator、接口请求集合),是处理批量异步操作的优雅方案:

javascript

运行

// 模拟批量接口请求
const fetchList = [
  fetch("/api/1"),
  fetch("/api/2"),
  fetch("/api/3")
];

// 异步遍历,按顺序执行
for await (const res of fetchList) {
  const data = await res.json();
  console.log(data);
}

七、总结表格(快速查阅)

场景推荐语法避坑要点
遍历普通对象属性for...in必须配合 hasOwnProperty 过滤原型链
遍历数组元素for...of无需索引,直接取值
遍历字符串字符for...of比 for...in 更直观
遍历 Set/Mapfor...ofMap 可直接解构 [key, value]
遍历 DOM 节点 / Argumentsfor...of天然支持,无需转数组
异步遍历可迭代对象for await...of需配合 Promise 使用
稀疏数组 / 带自定义属性的数组绝对不用 for...in用 for...of 或普通 for 循环