跳到主要内容

for...in 与 for...of:一篇彻底讲清“遍历键”与“遍历值”

在 JavaScript 里,for...infor...of 很像,但它们解决的根本不是同一类问题:

  • for...in遍历属性名(key)
  • for...of遍历可迭代对象产出的值(value)

如果你只记语法,很容易写出“能跑但不对”的代码;如果你把它们背后的规则搞清楚,很多面试题和实际 bug 都会变得很好判断。

这篇文档你会收获什么
  • 一眼分清 for...infor...of 到底在遍历什么
  • 知道它们分别适合数组、对象、字符串、Set、Map 的哪种场景
  • 理解“可枚举属性”和“可迭代对象”这两个关键词
  • 避开数组遍历、对象遍历里的高频坑
  • 文末附带一组高频面试问答

一、先记结论:别混着用

对比项for...infor...of
遍历结果属性名 / 键名
面向对象可枚举属性可迭代对象
常见返回值字符串键,如 "0""name"元素值,如 10"hello"
能否遍历数组能,但不推荐能,推荐
能否遍历普通对象不能(默认不可迭代)
能否遍历字符串不推荐推荐
能否遍历 Set / Map不能正常按值遍历可以
是否会看原型链会看可枚举的继承属性不关心原型链枚举,按迭代器产出值

你可以先用一句话粗暴记忆:

  • 想拿 key,先想到 for...in
  • 想拿 value,先想到 for...of

但这还不够,因为“key”和“value”只是表象,本质差异在下一节。


二、本质差异:一个看“属性”,一个看“迭代器”

2.1 for...in 遍历的是“可枚举属性名”

for...in 会遍历对象上可枚举(enumerable)的字符串属性,并且还会沿着原型链往上找。

先看一个简单例子:

const user = {
name: "Terry",
age: 18,
};

for (const key in user) {
console.log(key);
}

// name
// age

这里输出的是键名,不是值。要拿值,需要再取一次:

for (const key in user) {
console.log(key, user[key]);
}

// name Terry
// age 18

2.2 for...of 遍历的是“迭代器产出的值”

for...of 不关心对象上有哪些属性,它只关心:这个值是不是可迭代(iterable)

比如数组天然可迭代:

const nums = [10, 20, 30];

for (const value of nums) {
console.log(value);
}

// 10
// 20
// 30

这里拿到的是每一项的值,而不是索引。


三、为什么数组要优先用 for...of,不要用 for...in

这是最常见的面试点,也是最容易写错的地方。

3.1 for...in 遍历数组,拿到的是索引字符串

const arr = [100, 200, 300];

for (const key in arr) {
console.log(key, typeof key);
}

// 0 string
// 1 string
// 2 string

注意:这里的 key 是字符串,不是数字。

如果你只是想拿值,还得多写一步:

for (const key in arr) {
console.log(arr[key]);
}

3.2 数组上自定义属性时,for...in 也会把它遍历出来

const arr = [10, 20];
arr.extra = "额外信息";

for (const key in arr) {
console.log(key);
}

// 0
// 1
// extra

这通常不是你想要的结果。

for...of 只会遍历数组迭代器产出的元素值:

for (const value of arr) {
console.log(value);
}

// 10
// 20

3.3 for...in 还可能遍历到原型链上的可枚举属性

这是它更“危险”的地方。只要原型链上有可枚举属性,也可能被扫出来。

const base = { inherited: "来自原型" };
const obj = Object.create(base);
obj.own = "自己的属性";

for (const key in obj) {
console.log(key);
}

// own
// inherited

所以,数组遍历请优先用 for...of;对象遍历若用 for...in,通常要配合 Object.hasOwn() 过滤。


四、普通对象为什么不能直接用 for...of

很多人刚学时会写出这样的代码:

const user = { name: "Terry", age: 18 };

for (const item of user) {
console.log(item);
}

这会直接报错,因为普通对象默认不是可迭代对象

也就是说,它没有提供默认的迭代器,不知道应该按什么顺序吐出哪些值。

4.1 如果你想遍历对象,推荐这三种写法

写法 1:只要 key

const user = { name: "Terry", age: 18 };

for (const key of Object.keys(user)) {
console.log(key);
}

// name
// age

写法 2:只要 value

for (const value of Object.values(user)) {
console.log(value);
}

// Terry
// 18

写法 3:同时要 key 和 value

for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}

// name Terry
// age 18

实际开发里,如果你想要“像 for...of 一样”遍历对象,最常用的就是:

for (const [key, value] of Object.entries(obj)) {
// ...
}

这也是我最推荐你养成的对象遍历写法。


五、for...in 的正确使用姿势

for...in 不是不能用,而是要知道它到底适合什么。

5.1 适合场景:你明确就是要“对象的属性名”

const config = {
host: "localhost",
port: 3000,
open: true,
};

for (const key in config) {
if (Object.hasOwn(config, key)) {
console.log(key, config[key]);
}
}

这里的 Object.hasOwn(config, key) 很关键。

它表示:

  • 只处理对象自身的属性
  • 不把原型链上的可枚举属性算进来

5.2 更现代的建议

如果你不是特别需要 for...in 的语义,普通对象遍历更推荐:

for (const [key, value] of Object.entries(config)) {
console.log(key, value);
}

因为它更直观,也不会意外扫到继承属性。


六、for...of 能遍历哪些东西

只要一个值实现了迭代协议,for...of 就能遍历它。

常见可迭代对象有:

  • 数组 Array
  • 字符串 String
  • 集合 Set
  • 映射 Map
  • 某些类数组/DOM 集合(如很多环境下的 NodeList
  • 生成器对象 Generator

6.1 遍历字符串

for (const ch of "JS") {
console.log(ch);
}

// J
// S

6.2 遍历 Set

const set = new Set([1, 2, 2, 3]);

for (const value of set) {
console.log(value);
}

// 1
// 2
// 3

6.3 遍历 Map

Map 的默认迭代结果是 [key, value]

const map = new Map([
["name", "Terry"],
["age", 18],
]);

for (const [key, value] of map) {
console.log(key, value);
}

// name Terry
// age 18

七、如果我既想拿索引,又想拿值,怎么办?

这是 for...of 的另一个高频追问。

7.1 数组用 entries()

const arr = ["a", "b", "c"];

for (const [index, value] of arr.entries()) {
console.log(index, value);
}

// 0 a
// 1 b
// 2 c

这样写比 for...in 更稳,因为:

  • index 是真正的数字索引语义
  • value 直接可用
  • 不会扫到自定义属性和继承属性

八、for...infor...offorEach 怎么选

很多同学会把这三者一起问。你可以这样区分:

8.1 for...of

  • 适合:数组、字符串、Set、Map 等可迭代对象
  • 优点:语义自然,支持 breakcontinuereturn
  • 缺点:普通对象不能直接用

8.2 for...in

  • 适合:遍历对象属性名
  • 优点:能直接枚举键名
  • 缺点:会受可枚举属性、原型链影响;遍历数组不合适

8.3 forEach

  • 适合:数组的函数式遍历
  • 优点:写法短
  • 缺点:不能直接 break / continue / return 跳出外层循环

看一个对比例子:

const nums = [1, 2, 3, 4, 5];

for (const n of nums) {
if (n === 3) break;
console.log(n);
}

// 1
// 2

如果你想中途停止,for...of 往往比 forEach 更合适。


九、几个很容易混淆的细节

9.1 for...in 遍历到的是字符串键

const arr = [10, 20];

for (const key in arr) {
console.log(key, typeof key);
}

// 0 string
// 1 string

所以不要期待它天然就是数字索引。

9.2 for...in 不遍历 Symbol 键

const id = Symbol("id");
const obj = {
name: "Terry",
[id]: 1001,
};

for (const key in obj) {
console.log(key);
}

// name

如果你要拿到对象全部自有键(包括 Symbol),更适合用:

Reflect.ownKeys(obj);

9.3 for...of 的前提是对象可迭代

如果一个对象实现了 Symbol.iterator,它也可以被 for...of 遍历。只是普通对象默认没有这项能力

你可以先把这件事理解成:

  • for...in:看对象“有哪些可枚举属性名”
  • for...of:看对象“愿意按什么规则一个个吐出值”

十、开发中的推荐写法清单

10.1 遍历数组

for (const item of list) {
// 推荐
}

10.2 遍历数组并拿索引

for (const [index, item] of list.entries()) {
// 推荐
}

10.3 遍历普通对象

for (const [key, value] of Object.entries(obj)) {
// 推荐
}

10.4 明确要枚举对象键名

for (const key in obj) {
if (Object.hasOwn(obj, key)) {
// 可以用
}
}

10.5 尽量避免的写法

for (const key in arr) {
// 不推荐遍历数组
}

十一、一组例子帮你彻底记住

const obj = { a: 1, b: 2 };
const arr = [10, 20];
const str = "hi";

for (const key in obj) {
console.log("obj in:", key);
}

for (const value of arr) {
console.log("arr of:", value);
}

for (const ch of str) {
console.log("str of:", ch);
}

输出含义分别是:

  • obj in:对象的键名
  • arr of:数组的元素值
  • str of:字符串的每个字符

如果你能把这三句解释顺,就说明你已经真的分清了。


十二、高频面试问答

Q1:for...infor...of 的核心区别是什么?

A: for...in 遍历的是对象的可枚举属性名;for...of 遍历的是可迭代对象产生的值。一个面向“属性枚举”,一个面向“迭代协议”。

Q2:为什么不推荐用 for...in 遍历数组?

A: 因为它拿到的是字符串索引,而且还可能遍历出自定义属性、继承的可枚举属性,语义上不稳定。数组更适合用 for...offorEach、普通 for

Q3:为什么普通对象不能直接 for...of

A: 因为普通对象默认不是可迭代对象,没有内置的 Symbol.iterator。如果要遍历对象,通常先用 Object.keys()Object.values()Object.entries() 转成可迭代结果再配合 for...of

Q4:for...in 会遍历原型链上的属性吗?

A: 会。只要原型链上的属性是可枚举的,就可能被遍历到。所以实际开发里常配合 Object.hasOwn(obj, key) 过滤。

Q5:for...of 能不能遍历对象的 key?

A: 能,但不是直接遍历普通对象本身,而是遍历 Object.keys(obj) 的结果:

for (const key of Object.keys(obj)) {
console.log(key);
}

Q6:for...offorEach 相比有什么优势?

A: for...of 支持 breakcontinuereturn,更适合需要中途终止或跳过的场景;forEach 更偏函数式回调风格。

Q7:如何在 for...of 里同时拿到索引和值?

A:entries()

for (const [index, value] of arr.entries()) {
console.log(index, value);
}

十三、最后用 4 句话收尾

  1. for...in 遍历的是 key
  2. for...of 遍历的是 value
  3. 数组优先 for...of,对象优先 Object.entries() + for...of
  4. 如果你非要用 for...in 遍历对象,记得加 Object.hasOwn()

把这四句记住,这个知识点基本就稳了。