在 JavaScript
中,循环主要分为两种类型。
其中包括原生 for 循环和各类代码库的封装方法
一种是遍历时以序列递增/递减,以数量大小作为判断条件
基础语法为
1for (expression1; expression2; expressin3) {2// to do something3}
expression1 条件值初始化,在整个循环开始之前执行
expression2 定义条件,在每次循环之前判断执行
expression3 定义递增/递减规则,在每次循环之后执行
例如
10for (var i = 0; i < 6; i++) {20console.log(i)30}4050// 等价于60var i = 0;70for (; i < 6;) {80console.log(i)90i++10}
需要注意的是,当我们以 var 声明条件初始值时,for 循环不构成一个作用域。因此,当我们在循环之后访问该变量时,i 的值会变成 6
这里需要特别注意,i 的值之所以会是 6,是因为当 i = 5时,满足条件,进入执行当次循环逻辑,但是 expression3 会在每次循环之后执行,因此之后还会执行一次 i++,从而让 i 变成 6。这是许多人会忽略的知识点
如果我们希望循环的代码块构成一个作用域,可以使用 let/const
声明条件初始值
1arr.forEach()2arr.map()3arr.filter()4arr.some()5arr.every()6arr.find()7arr.reduce()8....
另外一种类型是不关注序列,只关注条件
for in 通常用来遍历常规对象的属性。他能够识别对象的所有可枚举属性
1var person = {2name: 'Toy',3age: 224}5for (var attr in person) {6console.log(attr, person[attr])7}
记住 for in 的特性,而不要总是在初学时搞一些骚操作,例如非要用 for in 去遍历数组,从语法上来说这不会有错,但这会增加你的学习负担,这个负担是无意义的,遍历出来的数组序列也是字符串的形式,甚至有可能是无序的,他与 for 循环不同
for of 用于遍历可迭代对象。
可迭代对象需要专门的篇幅去学习,这里我们不再扩展,只需要记住常见的可迭代对象包括数组,Map,Set,实践中,我们几乎不用 for of 来遍历数组
1for (variable of iterable) {2// TODO3}
1var a = new Map()23a.set('name', 'Toy')4a.set('age', 20)56for(var attr of a) {7console.log(attr, a.get(attr))8}
Map 和 Set 的实例都自带基于 for of 封装的 forEach
函数
初学时,对 while 循环的理解比较困难,因为大多数例子依然使用关注序列的方式去讲解他的语法,以至于学习者无法非常明确的感受到他与 for 循环之间的差别,从而导致了许多人在实践中很少使用到他。
1// bad example2// 从语法的角度上这个例子是可以的3var text = '0'4var i = 15while (i < 5) {6text += ',' + i7i++8}
这个例子从语法角度上来说是没有任何问题的,此时我们会发现他与 for 循环非常的相似,因此遇到这种情况我们可能更偏向于使用 for 循环。事实上,while 循环并不强调序列,只是当我们人为构造序列作为 while 的判断条件时,他也是可以实现的
我们来看另外一个例子
10// jobs 中的每一项都是函数20var jobs = [30() => { console.log(1) },40() => { console.log(2) },50() => { console.log(3) }60]7080// 我们的目标是执行所有函数,并且被执行的函数需要从数组中出来90var job10while(job = jobs.pop()) {11job()12}13console.log(jobs)
我们要确保 while 中的判断条件为 true,才能保证进入执行循环逻辑。但是当数组为空时,jobs.pop()
无法取到值,返回 undefined
,如下表达式的结果无法返回 ture,从而循环终止
1job = undefined2// undeifned -> false
这个例子有意思的地方就在于,当 jobs 中的某一项在执行的过程中,还可以向数组中添加新的子项
10var fn = () => {20console.log(4)30jobs.push(() => { console.log('add item') })40}5060var jobs = [70() => { console.log(1) },80() => { console.log(2) },90() => { console.log(3) },10fn11]1213// 我们的目标是执行所有函数,并且被执行的函数需要从数组中出来14var job15while(job = jobs.pop()) {16job()17}18console.log(jobs)
循环依然能够正常执行,并且能够达到最终让数组置空的目的。
该案例来自于 Promise 的实现原理中的一部分逻辑
do while 是 while 循环的变体。循环体逻辑写在 do 的后面,表示无论条件是什么,循环体逻辑都要先执行一次。当 while 的判断条件为 true 时,继续执行,否则终止执行
记住,该语法依然不关注序列,不关注循环执行次数
我们定义一个数组,该数组中包含了 10 个字母,在循环中产生一个随机数作为序列从数组中获取字母,当字母为 a
时,循环终止。
我们要统计要随机多少次才能取到目标字母,并且记录每一次循环取到的结果
因为有可能第一次循环就取到了目标字母,因此我们这里比较适合使用 do while
1var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']2var count = 03do {4var letter = letters[Math.floor(Math.random() * 10)]5console.log(letter)6count++7} while (letter != 'a')
当然,如果我们初始化目标字母为空字符串,也适合直接使用 while
1var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']2var count = 03var letter = ''4while (letter != 'a') {5var letter = letters[Math.floor(Math.random() * 10)]6console.log(letter)7count++8}
最后,学习循环基础,我们还需要知道 break, continue, return
在每个循环中的表现,实践时根据语义和需要自己尝试即可,这里就不在专门讲解啦