for循环的不可替代之处

337 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情

C语言风格的for循环编写起来很冗长,还需要定义一个额外的变量,并在整个过程中跟踪它。在有如此多的替代方法的情况下,为什么没有一个要废除for循环的提案呢?真相是for循环有其他循环不可替代的地方。

数组的.forEach方法

这可能是用的最多的循环方法,因为作为前端程序员,我们都喜欢函数式编程,所以当我们在使用.forEach时有一种洋洋得意的感觉,而且可能还有那么一点瞧不起其他循环方法的感觉。这个方法使得我们可以对数组的每个元素调用函数。如下所示:

const mynumbers = [1,2,3,4]

mynumbers.forEach(n => {
  console.log(n)
})

//会在不同的行打印出数字

这是处理数组元素的优雅方法,但是,如果逻辑是异步的,这种方法就无法处理了,来看看下面这个例子:

const mynumbers = [1,2,3,4]

const delay = async (ms) => new Promise(resolve => setTimeout(resolve, ms))


async function externalPrint(val) {
  await delay(100) //假装http请求
  console.log(val)
}

mynumbers.forEach(async (n) => {
  await externalPrint(n)
})

console.log("This line should come after all numbers")

执行结果如下: image.png

事与愿违,我们可以看到console.log("This line should come after all numbers"),这行代码居然比循环语句先执行,这不是我们想要的。原因是forEach 是 ES5 的 API,要比 ES6 的 Promise 要早的多得多。forEach 本身并不支持异步写法,你在 forEach 方法的前面加不加 await关键字都是无效的,因为它的内部没有处理异步的逻辑。为了向后兼容,所以forEach以后也不会支持异步处理。重要的事情说三遍,forEach 是 ES5 的 API,所以不支持异步逻辑。forEach 是 ES5 的 API,所以不支持异步逻辑。forEach 是 ES5 的 API,所以不支持异步逻辑。
另外这是一个数组方法,这意味着我们不能使用它来迭代对象的属性。除非我们先得到属性列表,比如Object.getOwnPropertyNames,但这是另外一回事。

for..offor..in循环

这两个方法非常相似,它们受欢迎的程度介于forforEach循环之间。为什么?因为它们提供的抽象级别介于forforEach之间。 让我们看看它们的用法:

const mynumbers = [1,2,3,4]

for(let i in mynumbers) {
  console.log(i)
}
//会打印出0到3

for(let i of mynumbers) {
  console.log(i)
}
//会打印出1到4

他们的区别在于for..in遍历的是数组的索引,而for.. of遍历的是数组元素值。那么它们是否兼容异步逻辑呢?一起来看看。

const mynumbers = [1,2,3,4]

const delay = async (ms) => new Promise(resolve => setTimeout(resolve, ms))


async function externalPrint(val) {
  await delay(100) //we fake the HTTP request here
  console.log(val)
}

for(let i in mynumbers) {  
    await externalPrint(i)
}

console.log("This line should come after all numbers")

这段代码的逻辑和前面一样,一起来看看运行结果: image.png

如上图,我们拿到了想要的结果,说明它们是兼容异步逻辑的,下面我们再测试一下,将这些循环用于对象而不是数组,是否会拿到预期的结果呢:

let myHash = {
  a: 1,
  b: 2,
  name: "bob"
}

for(let i in myHash) {
  console.log(i)
}
//会打印出 'a', 'b', 'name'

for(let i of myHash) {
  console.log(i)
}
//会打印出 '1', '2', 'bob'

可以看到,他们也能用于处理对象,这说明它们比 .forEach方法更强大更通用。但是和for语句相比,它们仍然存在缺陷:

  • 如果我们不是处理数组/对象,而只是要重复执行某段逻辑呢?此时总不可能去人为的构造一个数组/对象吧。

for循环

for循环通常采用以下形式使用:

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

但是千万别限制自己,看看下面的用法:

for(;;) {
  console.log("This will run forever")
}

for(let i =0; i < 10; console.log(++i)){}
//会打印出 0 to 9

for(let i =0, x=19; i < 10 || x > 5; i++){
 x--;
 console.log(i, x)
}

看看这3个非常有效且漂亮的FOR循环。前两个很简单。我们很容易理解。 但最后一个呢?你认为这个循环的输出是什么? 它从0开始,但x从19开始,每次迭代都会增加i,但也会减少x,直到i不小于10,x不大于5。

image.png

是的,并不是每个人都能猜到输出结果的。但是这样看吧。原因很多时候容易把||看成&&。 这种灵活性for让循环如此强大,同时有的时候也让人望而生畏。

该如何选择何种循环方法

  • 如果要处理数组中的元素列表,则首先考虑forEach方法(或其函数变体,比如map)。
  • 如果你想迭代一个对象,或者你想在每次迭代中执行一些异步函数,首先考虑一下for..offor..in
  • 如果你想迭代列表中的一个随机部分,或者你需要在循环中定义一个非常复杂的逻辑,那么请首先考虑for循环。 同时,我们也需要知道,灵活就意味着我们很容易写出晦涩的代码出来, 所以注释很重要。

参考:blog.bitsrc.io/why-do-java…