JavaScript 中,循环主要分为两种类型。

INFO

其中包括原生 for 循环和各类代码库的封装方法

一种是遍历时以序列递增/递减,以数量大小作为判断条件

for 循环

基础语法为

code.ts
1
for (expression1; expression2; expressin3) {
2
// to do something
3
}

expression1 条件值初始化,在整个循环开始之前执行

expression2 定义条件,在每次循环之前判断执行

expression3 定义递增/递减规则,在每次循环之后执行

例如

code.ts
1
for (var i = 0; i < 6; i++) {
2
console.log(i)
3
}
4
5
// 等价于
6
var i = 0;
7
for (; i < 6;) {
8
console.log(i)
9
i++
10
}

需要注意的是,当我们以 var 声明条件初始值时,for 循环不构成一个作用域。因此,当我们在循环之后访问该变量时,i 的值会变成 6

INFO

这里需要特别注意,i 的值之所以会是 6,是因为当 i = 5时,满足条件,进入执行当次循环逻辑,但是 expression3 会在每次循环之后执行,因此之后还会执行一次 i++,从而让 i 变成 6。这是许多人会忽略的知识点

如果我们希望循环的代码块构成一个作用域,可以使用 let/const 声明条件初始值

其他基于 for 循环的封装

code.ts
1
arr.forEach()
2
arr.map()
3
arr.filter()
4
arr.some()
5
arr.every()
6
arr.find()
7
arr.reduce()
8
....

另外一种类型是不关注序列,只关注条件

for in

for in 通常用来遍历常规对象的属性。他能够识别对象的所有可枚举属性

code.ts
1
var person = {
2
name: 'Toy',
3
age: 22
4
}
5
for (var attr in person) {
6
console.log(attr, person[attr])
7
}
INFO

记住 for in 的特性,而不要总是在初学时搞一些骚操作,例如非要用 for in 去遍历数组,从语法上来说这不会有错,但这会增加你的学习负担,这个负担是无意义的,遍历出来的数组序列也是字符串的形式,甚至有可能是无序的,他与 for 循环不同

for of

for of 用于遍历可迭代对象

INFO

可迭代对象需要专门的篇幅去学习,这里我们不再扩展,只需要记住常见的可迭代对象包括数组,Map,Set,实践中,我们几乎不用 for of 来遍历数组

code.ts
1
for (variable of iterable) {
2
// TODO
3
}
code.ts
1
var a = new Map()
2
3
a.set('name', 'Toy')
4
a.set('age', 20)
5
6
for(var attr of a) {
7
console.log(attr, a.get(attr))
8
}

Map 和 Set 的实例都自带基于 for of 封装的 forEach 函数

while

初学时,对 while 循环的理解比较困难,因为大多数例子依然使用关注序列的方式去讲解他的语法,以至于学习者无法非常明确的感受到他与 for 循环之间的差别,从而导致了许多人在实践中很少使用到他。

code.ts
1
// bad example
2
// 从语法的角度上这个例子是可以的
3
var text = '0'
4
var i = 1
5
while (i < 5) {
6
text += ',' + i
7
i++
8
}

这个例子从语法角度上来说是没有任何问题的,此时我们会发现他与 for 循环非常的相似,因此遇到这种情况我们可能更偏向于使用 for 循环。事实上,while 循环并不强调序列,只是当我们人为构造序列作为 while 的判断条件时,他也是可以实现的

我们来看另外一个例子

code.ts
1
// jobs 中的每一项都是函数
2
var jobs = [
3
() => { console.log(1) },
4
() => { console.log(2) },
5
() => { console.log(3) }
6
]
7
8
// 我们的目标是执行所有函数,并且被执行的函数需要从数组中出来
9
var job
10
while(job = jobs.pop()) {
11
job()
12
}
13
console.log(jobs)

我们要确保 while 中的判断条件为 true,才能保证进入执行循环逻辑。但是当数组为空时,jobs.pop() 无法取到值,返回 undefined,如下表达式的结果无法返回 ture,从而循环终止

code.ts
1
job = undefined
2
// undeifned -> false

这个例子有意思的地方就在于,当 jobs 中的某一项在执行的过程中,还可以向数组中添加新的子项

code.ts
1
var fn = () => {
2
console.log(4)
3
jobs.push(() => { console.log('add item') })
4
}
5
6
var jobs = [
7
() => { console.log(1) },
8
() => { console.log(2) },
9
() => { console.log(3) },
10
fn
11
]
12
13
// 我们的目标是执行所有函数,并且被执行的函数需要从数组中出来
14
var job
15
while(job = jobs.pop()) {
16
job()
17
}
18
console.log(jobs)

循环依然能够正常执行,并且能够达到最终让数组置空的目的。

INFO

该案例来自于 Promise 的实现原理中的一部分逻辑

do while

do while 是 while 循环的变体。循环体逻辑写在 do 的后面,表示无论条件是什么,循环体逻辑都要先执行一次。当 while 的判断条件为 true 时,继续执行,否则终止执行

记住,该语法依然不关注序列,不关注循环执行次数

我们定义一个数组,该数组中包含了 10 个字母,在循环中产生一个随机数作为序列从数组中获取字母,当字母为 a 时,循环终止。

我们要统计要随机多少次才能取到目标字母,并且记录每一次循环取到的结果

因为有可能第一次循环就取到了目标字母,因此我们这里比较适合使用 do while

code.ts
1
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
2
var count = 0
3
do {
4
var letter = letters[Math.floor(Math.random() * 10)]
5
console.log(letter)
6
count++
7
} while (letter != 'a')

当然,如果我们初始化目标字母为空字符串,也适合直接使用 while

code.ts
1
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
2
var count = 0
3
var letter = ''
4
while (letter != 'a') {
5
var letter = letters[Math.floor(Math.random() * 10)]
6
console.log(letter)
7
count++
8
}

最后,学习循环基础,我们还需要知道 break, continue, return 在每个循环中的表现,实践时根据语义和需要自己尝试即可,这里就不在专门讲解啦

专栏首页
到顶
专栏目录