作者 | Fernando Doglio 译者 | 王强

我们大家都至少会了解一个版本的 For 循环,它如此经典,可能每一种语言都有它的一个版本。但 JavaScript 足足有 3 种 For 循环(细究的话是 4 种),这 3 种版本还不是完全一样,具体来说分别是:

  • 经典的 For 循环;

  • For…of 和 For…in 这对;

  • 还有精美的函数版本:.forEach。

这三种版本之间有很多区别,因此在本文中,我想介绍它们的具体内容,以及如何或何时使用它们才能获得最佳结果。那就开始吧。

这三种版本之间有很多区别,因此在本文中,我想介绍它们的具体内容,以及如何或何时使用它们才能获得最佳结果。那就开始吧。

小技巧:使用 Bit组件 和它的所有依赖项和设置 封装 在一起。通过更好的代码复用、更简单的维护和更少的开销构建真正的模块化应用程序:

https://bit.dev/?utm_medium=content&utm_source=bitsandpieces&utm_content=1&utm_campaign=campaign&source=post_page-----f0fb5501bdf3----------------------

经典的 For 循环

首先来看经典的 For 循环;你可以在其中定义内部计数器,设置中断条件和步进更改(通常是增加或减少计数器计数)。

具体的语法为:

for([计数器定义];[条件中断定义];[步进定义]){//……这里是重复的代码}

以前你肯定写过这种代码,最常见的形式是:

for(let counter = 0; counter < 10; counter++) {console.log(counter)}

虽说这种代码可以运行得很好,但是 For 循环的各个部分要更灵活一些。实际上,你应该将它们视为:

for([EXPRESSION EXECUTED ONLY ONCE AT THE START OF THE LOOP(表达式仅在循环开始时执行一次)];[BOOLEAN CONDITION CHECKED ON EVERY STEP(每一步都检查布尔条件)];[EXPRESSION EXECUTED ON EVERY STEP OF THE LOOP(表达式在循环的每一步都执行)])

也就是说,你可以使用多个计数器进行 For 循环,或者在不一定影响计数器的情况下执行每一步的代码。这里举几个例子。例如,这是一个完全有效的循环:

for(let a = 0, b = 0; a < 10 && b < 100; a++, b+=10) {   console.log(a, b)}/*0 01 102 203 304 405 506 607 708 809 90*/

你甚至可以更进一步,从上面的一般用例中跳出来:

for(let a = 0, b = 0; a < 10 && b < 100; console.log("Your counters are at:", ++a, b+=2)){}/*Your counters are at: 1 2Your counters are at: 2 4Your counters are at: 3 6Your counters are at: 4 8Your counters are at: 5 10Your counters are at: 6 12Your counters are at: 7 14Your counters are at: 8 16Your counters are at: 9 18Your counters are at: 10 20*/

只要你记得该函数的返回值将被强制转换为布尔值,就可以用中间表达式代替函数调用。

function isItDone(a) { console.log("fn called!") return a < 10}

for(let a = 0; isItDone(a); a++) { console.log(a)}/*fn called!0fn called!1fn called!2fn called!3fn called!4fn called!5fn called!6fn called!7fn called!8fn called!9fn called!*/

那么如何在经典的 For 循环中处理 异步代码 呢?多亏了我们的新朋友 async/await,这很容易:

const fs = require("fs")

async function read(fname) {    return new Promise( (resolve, reject) => {        fs.readFile(fname, (err, content) => {            if(err) return reject(err)            resolve(content.toString())        })    })}

(async () => {    let files = ['file1.json', 'file2.json']

    for(let i = 0; i < files.length; i++) {        let fcontent = await read(files[i])        console.log(fcontent)        console.log("-------")    }})()

注意,这里我们可以简单地使用循环,就好像背后没有异步机制一样。这就是 async/await,因为有它,我们才能重新依靠诸如 For 循环 之类的基本构造来迭代一组异步指令。过去,如果你想使用回调或 Promise 实现同一目标,则逻辑会复杂得多。这就是诸如 async.js 之类的库诞生的起源。

顺便说一句:在我的示例中,for 循环位于 IIFE内;你可能已经知道,这只是因为 await 指令需要位于异步函数内,否则 Node 是不会允许它的。

For…in 和 For…of

是的,它们是上一版本的非常相似的变体,但它们并不是同一种循环。

给一个简短的定义:

For…in 循环处理对象的非符号可枚举属性(这里的关键字是“对象”,因为 JavaScript 中的几乎所有内容都是对象)。当你将自定义对象用作哈希图或字典时(这是非常常见的做法),这非常有用。

但请 注意,迭代是按任意顺序完成的,因此不要依赖循环来选择所需的正确顺序,并且在有必要的时候一定要自己控制好这个部分。

let myMap {
  uno: 1,
  dos: 2,
  tres: 3
}for(let key in myMap) {console.log(key, "=", myMap[key]);
}/*
uno = 1
dos = 2
tres = 3
*/

很简单,不是吗?但请注意,因为就像我说的那样,JavaScript 中的几乎所有内容都是一个对象,因此你可以在确实需要 For…of 的时候结束 For…in。例如,如果你要遍历一个字符串(它是一个对象)中的每个字符,那么如果你使用 For…in 将会发生以下情况:

for(let k in "Hello World!") {   console.log(k)}/*01234567891011*/

我们没有遍历字符串的每个字母,而是遍历了每个属性;正如你所看到的,我们实际上是在处理与数组非常相似的结构(对于字符串类型)。这是有道理的,因为"Hello World!"[1] 不仅能正常执行,还可以返回这个位置的实际字符(也就是字母“e”)。相反,如果你想遍历每个字符,则需要使用另一个变体:For…of

for(let char of "Hello World!") {  console.log(char)}/*HelloWorld!*/

现在是不是更清楚一些了?用例是一样的,但是有了它,你就可以访问可迭代的值(字符串是可迭代的,数组、映射、集合和 arguments 或 NodeList 这样类似数组的结构也是可迭代的);当然也包括你自己的对象,只要把它们定义为可迭代即可。拿上面的示例来说,没有直接的方法来获取循环的当前索引,当然除非你在循环之外定义它并在每个步骤上都更新它;或者你可以针对数组使用 entries 方法,则可以同时获取索引和值,如下所示:

let myArr = ["hello", "world"]for([idx, value] of myArr.entries()) {    console.log(idx, '=', value)}/*0 '=' 'hello'1 '=' 'world'*/

最后作为对比,看看异步代码的情况:一模一样!

const fs @= require("fs")

async function read(fname) {    return new Promise( (resolve, reject) => {        fs.readFile(fname, (err, content) => {            if(err) return reject(err)            resolve(content.toString())        })    })}

(async () => {    let files = ['file2.json', 'file2.json']

    for(fname of files) {        let fcontent = await read(fname)        console.log(fcontent)        console.log("-------")    }

    for(idx in files) {        let fcontent = await read(files[idx])        console.log(fcontent)        console.log("-------")    }})()

两个循环使用 await 构造时的反应方式完全相同,从而使你可以编写更简单、更简洁的代码。

优雅的函数式.forEach 循环

这可能是我最喜欢的一个,只是因为我非常喜欢声明式语法或通过声明式风格代替命令式来编写代码。而且,尽管前面两个版本的循环很好用,并且都有很好的用例,但它们也是非常命令式的风格,我们需要编写 我们的数据应该发生什么事情,而不是简单地编写 我们想要数据发生什么事情

不管怎么说,撇开哲学上的争论,.forEach 方法是 For 循环的另一个版本;但这种方法是数组对象的一部分,并且在执行函数时要接收一个函数和一个额外的可选参数来重新定义函数的上下文。

对于数组中的每个元素,我们的函数都会执行,并且会收到 三个参数(是的,确实是 三个,而不是你习惯使用的一个)。它们分别是:

  1. 当前正在处理的元素。

  2. 元素的索引,这简化了我们尝试使用 For…of 循环完成的任务

  3. 实际正在处理的数组。以防万一你要对它做什么事情。

下面我们来看一个简单的示例:

a = ["hello", "world"]

a.forEach ( (elem, idx, arr) => {   console.log(elem, "at: ", idx, "inside: ", arr)})/*hello at:  0 inside: [ 'hello', 'world' ]world at:  1 inside: [ 'hello', 'world' ]*/

简单明了,你可以看到我们是怎样在函数内轻松使用所有属性的。下面这个示例针对的是,你希望在 forEach 方法上使用第二个可选参数的情况:

class Person {    constructor(name) {        this.name = name    }}

function greet(person) {    console.log(this.greeting.replace("$", person.name))}

let english = {    greeting: "Hello there, $"}let spanish = {    greeting: "Hola $, ¿cómo estás?"}

let people = [new Person("Fernando"), new Person("Federico"), new Person("Felipe")]

people.forEach( greet, english)people.forEach( greet, spanish)

通过覆盖被调用函数 greet 的上下文,我可以在不影响其代码的情况下更改其行为。最后,为了表明这一方法也可以与异步代码一起使用,下面是示例:

const fs = require("fs")

async function read(fname) {    return new Promise( (resolve, reject) => {        fs.readFile(fname, (err, content) => {            if(err) return reject(err)            resolve(content.toString())        })    })}

let files = ['file1.json', 'file2.json']

files.forEach( async fname => {    let fcontent = await read(fname)    console.log(fcontent)    console.log("-------")})

请注意,由于我将回调声明为异步,因此不再需要 IIFE。

总结

我要分享的关于 JavaScript 中 For 循环的全部信息就是这些了,我希望现在你对它们有了更清晰的了解,并可以基于这些知识和当前的编码需求来选择自己喜欢的循环。

有任何意见和建议,欢迎发表评论并分享本文!

延伸阅读

https://blog.bitsrc.io/3-flavors-of-the-for-loop-in-javascript-and-when-to-use-them-f0fb5501bdf3

foreach循环怎么获取全部返回值_JavaScript中For循环的3种版本和使用场景相关推荐

  1. foreach循环怎么获取全部返回值_PHP跳出循环的方法语句有哪些

    php中文网最新课程 每日17点准时技术干货分享 对于即将步入PHP岗位的求职者来说,在面试过程中除了自我介绍相关经验外,更多的是PHP面试题的回答测试.本篇文章就给大家介绍在PHP面试过程中比较常见 ...

  2. js循环判断有无重复值_JavaScript中的while循环

    在 JavaScript 语言中,当我们使用 while 循环时,只要指定条件为 true,循环就可以一直执行. 并且只要条件一直满足,就可以实现一个无限循环,例如: while(true){ con ...

  3. foreach循环怎么获取全部返回值_jmeter基础逻辑控制器之ForEach控制器

    关于jmeter中的逻辑控制器有很多是可以帮助我们在接口测试中解决很多问题的,今天我们来了解一下ForEach控制器,看到这个控制器很多人第一反应是for循环,实际原理也差不多,但是并不等同于jmet ...

  4. js 用下标获取map值_javascript怎么获取map的值?

    Map对象保存键/值对,是键/值对的集合.任何值(对象或者原始值) 都可以作为一个键或一个值.Object结构提供了"字符串-值"的对应,Map结构提供了"值-值&quo ...

  5. for循环只执行一次_Python中for循环和while循环有什么区别?

    for循环和while循环有什么区别?众做周知,循环是Python中最基础也是最常见的知识点之一,下面我们来一起好好学习一下for循环和while循环,并对比分析两者的使用区别,帮助Python初学者 ...

  6. php 怎么循环数组取有值的,php怎么循环数组取有值的-PHP问题

    php 怎么循环数组取有值的 1.使用foreach或者while的,利用这两个语法结构来输出有值的项或删除数组中的空元素,简单代码如下:$v){ if( $v ){ echo $arr[$k]; } ...

  7. c语言中循环结构的作用,C语言中对于循环结构优化的一些入门级方法简介

    一.代码移动 将在循环里面多次计算,但是结果不会改变的计算,移到循环外面去. 例子: 优化前: void lower1(char *s){ int i; for(i=0;i if(s[i]>=' ...

  8. python遍历循环怎么理解_聊聊python中的循环遍历

    python之循环遍历 关于循环遍历大家都知道,不外乎for和while,今天我在这写点不一样的循环和遍历.在实践中有时会遇到删除列表中的元素,那么循环遍历列表删除指定元素该怎么做呢? 还是直接上代码 ...

  9. java循环语句有哪三类_java中的循环语句有哪些

    Java中有三种主要的循环结构: while 循环 do-while 循环 for 循环 顺序结构的程序语句只能被执行一次.如果您想要同样的操作执行多次,,就需要使用循环结构. 一.while循环 语 ...

最新文章

  1. VMware三种上网模型
  2. crashpad 应用程序异常解决方案
  3. 小球大作战-搜索优化
  4. Vue中Web组态实现方案-WebTopo的使用
  5. 洛谷P2462 [SDOI2007]游戏(哈希+最长路)
  6. 小汤学编程之JAVA基础day10——常用类(二):String常用方法、正则、StringBuffer和StringBuilder、Math和Random类、日期类和数字类
  7. 10.java之父被B站学习者下载达7000万次的Java视频教程你还没有看过知乎
  8. css3实现loading动画效果
  9. 【数学建模】2018 A题 高温作业专用服装设计(8.22-8.24训练)
  10. K8S还没用,又出个K9S,什么鬼?
  11. 哪些植物最吸毒?这份植物“吸毒”手册千万要收好!
  12. 手机便签怎么对待办分类文件夹加密
  13. 常用贴片器件正负极区分
  14. 【Matplotlib】学术论文黑白柱状图绘制
  15. android MDE工程师,Android Application Addon(插件) 架构及管理
  16. 两种方法筛选出多因子量化选股模型
  17. 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——3.2 动态数据流...
  18. linux卡利系统设置密码,Windows 10 Windows Linux子系统安装指南-官网
  19. 江苏无锡14岁女孩被推荐上北大(图)
  20. 怎么找生物信息论文的数据,PubMed太有用了!

热门文章

  1. celery delay 没反应
  2. 用 Win7,硬件也有新天地
  3. Linux 设置时区
  4. 移动端前端常见的触摸相关事件touch、tap、swipe等整理
  5. Struts2 标签配置详细
  6. android配置开发环境ubuntu
  7. 执行存储过程出现:不是有效的标识符。
  8. C# Winform 未能加载文件或程序集System.Data.SQLite或它的某一个依赖项。试图加载格式不正确的程序...
  9. C# Activator
  10. mate40pro什么时候用鸿蒙,mate40Pro什么时候可以用鸿蒙