JavaScript 循环详解:for...in、for...of 与数组方法差异

作者:木棉

时间:2026-10-24

浏览:389

本文深入解析 JavaScript 中可枚举属性与可迭代对象的区别,详细对比 for...in 与 for...of 循环的行为及适用场景。同时分析 forEach 与 map 方法在返回值、函数式编程支持及执行性能上的差异,帮助开发者根据实际需求选择最高效的遍历方式。

文章目录

前言

在 JavaScript 中处理循环逻辑时,必须明确区分两个核心概念:可枚举属性(enumerable properties)和可迭代对象(iterable objects)。

可枚举的属性

可枚举对象的一个关键特征是,当通过赋值运算符向对象添加属性时,其内部的 enumerable 标志默认被设为 true。

不过,开发者可以通过特定方式将这一标志修改为 false,从而改变默认行为。

一个实用的经验法则是:可枚举属性一定会出现在 for...in 循环的遍历结果中。

具体示例如下:


const users = {}
users.languages = 'JavaScript'
​
Object.getOwnPropertyDescriptor(users, 'languages')
// output -> { value: 'JavaScript', writable: true, enumerable: true, configurable: true }
​
// 在循环中对我们使用的属性进行更多的控制
Object.defineProperty(users, 'role', { value: 'Admin', writable: true, enumerable: false })
​
for (const item in users) {
  console.log(item) // languages
}

从代码可见,我们为 users 对象添加了 languages 属性。通过 Object.getOwnPropertyDescriptor 方法查看该属性的描述符,发现其 enumerable 值为 true。

接着使用 Object.defineProperty 添加 role 属性,并显式将 enumerable 设为 false。结果在 for...in 循环中并未输出 role 属性,这证实了 for...in 仅遍历可枚举属性。

可迭代对象

若一个对象定义了自身的迭代行为,则它属于可迭代对象。在 for...of 结构中循环的值由该对象的迭代行为决定。JavaScript 内置的可迭代类型包括 Array、String 和 Set;而 Map 对象因未指定 @iterator 方法,故不可迭代。

总体而言,JavaScript 中所有可迭代对象必然也是可枚举对象,但反之不成立。

可以从概念上理解:for...in 用于查找对象中的数据,而 for...of 用于遍历重复的序列。

以下示例展示了它们在 Array 数据类型中的具体表现:


const languages = ['JavaScript', 'Python', 'Go']
​
// 与 for...in 循环一起使用
for (const language in languages) {
  console.log(language)
}
// output
// 0
// 1
// 2
​
// 与 for...of 循环一起使用
for (const language of languages) {
  console.log(author)
}
// output -> JavaScript Python Go

使用此类构造时需注意,若 typeof 返回 object,则可以使用 for...in 循环。

对 languages 变量进行 typeof 操作的结果如下:


typeof languages // "object" -> 因此我们可以在中使用 for

这乍看可能令人意外,但需明确数组是一种特殊类型的对象,其键为索引。理解 for...in 在构造中查找对象的机制,有助于我们更好地使用它。当 for...in 遇到对象时,它会遍历该对象的每个键。

我们可以将 for...in 在 languages 数组上的遍历过程可视化如下:


const languages = {
  0: 'JavaScript',
  1: 'Python',
  2: 'Go'
}

注意:如果属性可追溯至对象(或从对象原型链继承),for...in 将以不确定的顺序遍历这些键。

相比之下,若对象实现了迭代器,for...of 构造将在每次迭代中直接循环遍历该值。

forEach 与 map 方法对比

虽然 forEach 和 map 方法都能实现类似目标,但它们在行为机制和性能表现上存在显著差异。

在基础层面,当函数被调用时,两者均接收一个回调函数作为参数。

参考以下代码片段:


const scoresEach = [2, 4 ,8, 16, 32]
const scoresMap = [2, 4 ,8, 16, 32]
const square = (num) => num * num

下面详细阐述它们在操作层面的具体差异。

主要区别在于返回值:forEach 返回 undefined,而 map 返回一个新数组:


let newScores = []
const resultWithEach = scoresEach.forEach(score => {
  const newScore = square(score)
  newScores.push(newScore)
})
​
const resultWithMap = scoresMap.map(square)
​
console.log(resultWithEach) // undefined
console.log(resultWithMap) // [4, 16, 64, 256, 1024]

Map 被视为纯函数,而 forEach 则执行突变操作:


console.log(newScores) // [4, 16, 64, 256, 1024]

从函数式编程的角度来看,map 更具优势。我们无需像使用 forEach 那样必须通过突变 newScores 变量来获取结果;只要输入相同,map 每次运行都会产生相同的结果。相反,forEach 的执行结果可能依赖于之前突变产生的值。

链式调用

使用 map 支持链式调用,因为其返回结果是数组。因此,可以立即对结果调用 filter、reduce、some 等其他数组方法。这在 forEach 中无法实现,因为其返回值为 undefined。

性能

在性能方面,map 方法通常优于 forEach 方法。

通过对比使用 map 和 forEach 实现的等效代码块的性能,平均而言,map 函数的执行速度至少快 50%。

结论

综上所述,在所有讨论的循环结构中,for...of 循环提供了最高的控制力。它可以与 return、continue 和 break 关键字配合使用,这意味着我们可以精确指定对数组中每个元素的操作,并决定是否可以提前退出或跳过某些元素。

本文介绍了 JavaScript 中不同循环结构的差异,更多相关内容请查阅本站过往文章或继续浏览后续文章,感谢大家的支持。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章