for...in 和 for...of 是 JavaScript 中两种常用的遍历语法,核心差异体现在遍历目标、遍历内容、适用场景上,且 for...of 是 ES6 新增特性,解决了 for...in 的诸多痛点,以下从核心区别、详细特性、使用场景、常见坑点四个维度全面解析,附实用示例。
一、核心核心区别(一句话总结)
| 特性 | for...in | for...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/Map | for...of | Map 可直接解构 [key, value] |
| 遍历 DOM 节点 / Arguments | for...of | 天然支持,无需转数组 |
| 异步遍历可迭代对象 | for await...of | 需配合 Promise 使用 |
| 稀疏数组 / 带自定义属性的数组 | 绝对不用 for...in | 用 for...of 或普通 for 循环 |