JavaScript 中有很多循环,比如for
语句、Array.prototype.forEach
以及for-in
、for-of
、for-await-of
语句。接下来结合规范学习总结它们。
for 语句
规范中,把运行时的for
语句分为三种形式.
for ( ; ; )
基本形式for (var...)
带有var
声明的形式for (let/const ...)
带块级作用域变量声明的形式
基本形式
运行时语义
- 如果
Expression1
存在
- 如果是表达式,对其进行求值,然后被舍弃(禁止行副作用,如赋值)
- 如果
Expression2
存在
- 则将其作为循环继续的条件
- 不存在则无判断条件(即不会退出循环)
- 如果
Expression3
存在
- 在当前循环体执行完后对其进行求值
带变量声明的形式
对于在for
循环中进行变量声明的另外两种形式:
- 对于
let
和const
声明,每次循环迭代都会创建一个新的词法环境(LexicalEnvironment),绑定新的变量 - 对于
var
声明,变量属于函数作用域,不随每次循环迭代创建

Array 中的循环
对于数组的绝大多数包含遍历的方法,内部逻辑都是: 首先获取数组的长度。于是在 forEach
中 callback
中
- 如果增加了数组项,访问到的也是旧数组的长度
- 如果在某个下标前修改了他,则后续访问的是修改后的值
- 如果删除数组中的某一项,由于会导致后续访问时某一项值为
undefined
,所以循环次数会减少(callback
不会执行)

HasProperty
: 判断O
中 是否包含P
这个属性键,如果包含则返回true
否则返回false
。 一般用作检查P
是否是对象O
本身的属性或者从O
的原型链上集成的属性。
所以一些面试问到 如何退出forEach
循环,本身 forEach
内置函数正常情况下都会遍历整个数组,除开使用 for
或者 for-of
(脱离题意)之外,只能考虑在数组的回调中抛出错误。当然,如果只是想不执行回调,也可以在callback
中提前返回undefined
。
for-in/for-of/for-await-of
规范中是分为 ForIn/OfHeadEvaluation 和 ForIn/OfBodyEvaluation 两部分来解释的。
for-in
- 将目标值转换为对象
- 首先将目标值
exprValue
转换为对象。如果exprValue
是原始类型(如字符串、数字),则通过ToObject
转换成对应的包装对象 - 如果目标值是
null
或undefined
,则返回Completion Record { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }
(即啥事也不干)
- 获取可枚举属性迭代器
- 调用
EnumerateObjectProperties
,它返回一个迭代器对象,该迭代器按顺序提供对象自身及其原型链上的 可枚举属性键
- 逐一迭代属性键
- 每次循环,调用迭代器的
next()
方法获取下一个属性键
- 终止条件
- 当迭代器返回
done: true
时,退出循环
for-of
基本上与 for-in
一致,区别在于循环体中获取的是 next().value
而非 next()
.
需要注意的是
- 如果目标值是
null
或者undefined
,则会抛出TypeError
null/undefined is not iterable
for-await-of
与 for-of
主要区别在于使用的是 asyncIterator
获取迭代器.
- 获取异步迭代器
- 调用目标对象的
@@asyncIterator
方法获取异步迭代器
- 等待异步值
- 调用异步迭代器的
next()
,返回Promise
- 使用
await
等待Promise
完成,获取其value
和done
- 执行循环体
- 终止条件
- 当迭代器返回
done: true
时,循环终止
需要注意的是
for-await-of
必须在async
函数内执行- 如果目标值是
undefined
或null
,则会抛出TypeError
Cannot read properties of undefined (reading 'Symbol(Symbol.asyncIterator)')