为什么 forEach 比常规迭代器更受欢迎?

IT技术 javascript for-loop ecmascript-6
2021-03-12 05:11:21

我正在阅读airbnb javascript 指南有一个特别的声明,它说:

不要使用迭代器。更喜欢 JavaScript 的高阶函数,而不是像 for-in 或 for-of 这样的循环。

他们给出上述声明的原因是:

这强制执行我们的不可变规则。处理返回值的纯函数比副作用更容易推理。

我无法区分给出的两种编码实践:

    const numbers = [1, 2, 3, 4, 5];

    // bad
    let sum = 0;
    for (let num of numbers) {
     sum += num;
    }
    sum === 15;

    // good
    let sum = 0;
    numbers.forEach((num) => {
     sum += num;
    });

    sum === 15;

谁能解释一下,为什么forEach比常规for循环更受欢迎?它如何真正产生影响?使用常规有任何副作用iterators吗?

6个回答

这种推理的Airbnb风格指南适用于用于不变性阵列的方法,这是filtermapreduce等,但并不forEach

这强制执行我们的不可变规则。处理返回值的纯函数比副作用更容易推理。

所以比较更像是:

// bad
let sum = 0;
for (let num of numbers) {
 sum += num;
}
sum === 15;

// bad
let sum = 0;
numbers.forEach((num) => {
 sum += num;
});

sum === 15;

// good
const sum = numbers.reduce((num, sum) => sum += num, 0);

sum === 15;

一般来说,for > forEach > for..of > for..in就性能而言。这种关系在几乎所有引擎中都是一致的,但对于不同的阵列长度可能会有所不同

forEach是在最新的 Chrome/V8 中得到显着改进的一个(几乎两次,基于这个综合测试):

由于它们都很快,除非另有证明,否则仅仅因为性能原因选择不太合适的循环方法可以被认为是初步优化。

forEach比较的主要好处for..of是前者即使在 ES3 中也是 polyfill 并提供值和索引,而后者更具可读性,但应该在 ES5 及更低版本中转译。

forEach已知的缺陷使其在某些情况下不适合使用for正确处理for..of

  • 回调函数创建新的上下文(可以用箭头函数寻址)

  • 不支持迭代器

  • 不支持发电机yieldasync..await

  • 没有提供提前终止循环的正确方法 break

它可以。但它本质上并不是一成不变的。不会自动为您提供新值(如 withreducemap)。您在其他地方(sum在您的示例中)定义的循环内改变了一个变量您可以以相同的方式修改现有对象,从而破坏“不可变规则”(好吧,您也可以这样做reduce,但通常被认为是错误的)。for并且forEach同样是“坏的”,根据这个推理,forEach具有函数的事实在这方面没有任何影响,因为我们无法从该函数返回结果。
2021-04-29 05:11:21
我没看明白This reasoning in Airbnb style guide applies to array methods that are used for immutability, which are filter, map, reduce, etc. but not forEach:为什么forEach不能用于不变性的开头行
2021-05-17 05:11:21
我认为这种推理是有效的(尽管通常更喜欢for..of可读性),但不是他们得出的结论。github.com/airbnb/javascript#iterators-and-generators一章是矛盾和不一致的。在一个地方,它指出应该避免副作用,然后他们在 forEach 内部产生副作用并将其标记为“好”。请记住,这只是风格指南。风格指南固执己见,并不总是有意义。
2021-05-17 05:11:21

这是没有意义的。forEach永远不应该是首选。是的,使用mapreducefilter比使用副作用操纵的东西循环干净多了。

但是,当您出于某种原因需要使用副作用时,则for … infor … of是惯用的循环结构。它们更容易阅读,速度也一样快,并强调您有一个带有副作用的循环体,forEach而它的回调看起来很实用,但实际上却不是。其他优点forEach包括您可以const用于迭代元素,并且可以在循环体中使用任意控制结构(return, break, continue, yield, await)。

NB埃里克利珀基本上已经说过类似的foreach对比.ForEach(...)在C#:blogs.msdn.microsoft.com/ericlippert/2009/05/18/...好像制作的Airbnb人都陷入思维的东西的陷阱,看起来功能最好只因为它看起来很实用。
2021-04-30 05:11:21

大多数时候,Airbnb 风格指南试图保持一致。这并不意味着永远没有理由使用 for 循环或 for-in 循环,就像假设您想尽早跳出循环一样。

当使用 for 循环来改变某些东西的值时,不变性开始发挥作用。就像将数组减少为整数或映射元素以生成新数组一样。使用内置的map, reduce, orfilter不会直接直接改变数组的值,所以它是首选。使用forEach过的/ for-in循环使用强制高阶函数在一个迭代的风格的一致性,所以我相信它为什么被推荐的。

forEach不会固有地改变数组的值。我的意思是,与mapor不同filterforEach将始终返回undefined,它没有返回值,可用于改变数组(应该更好地说明这一点)。更新。
2021-04-30 05:11:21
你能解释的声明This isn't the same for forEach为什么会forEach发生变异阵列的value相对于其他方法,如mapreduce等?
2021-05-17 05:11:21

forEach 迭代不能拖await这意味着您不能使用forEach管道,比如说,对服务器的 Web 请求。

forEach就是不存在的async Iterables

考虑以下情况:

1

let delayed = (value)=>new Promise(resolve => setTimeout(() => resolve(value), 2000));


(async ()=>{
    for(el of ['d','e','f'])console.log(await delayed(el))
    
})();


(async()=>{
    
    ['a','b','c'].forEach(async (el)=>console.log(await delayed(el)))
   
})();

结果:

d
a
b
c
e
f

[d,e,f]array的元素每两秒打印一次

[a,b,c]array的元素在两秒后一起打印

2

let delayed = (value)=>new Promise(resolve => setTimeout(() => resolve(value), 2000));

async function* toDelayedIterable(array) {
    for(a of array)yield (await delayed(a))    
}


for await(el of toDelayedIterable(['d','e','f']))console.log(el)

toDelayedIterable(['a', 'b', 'c']).forEach(async(el)=>console.log(await el));

结果:

d
e
f
Uncaught TypeError: toDelayedIterable(...).forEach is not a function

[d,e,f]array的元素每两秒打印一次

试图访问时的错误forEachasyncIterator[a,b,c]阵列。

以下是我在两者之间看到的优势列表。

“for of”的优点:

  1. 在开发工具中,只需悬停即可查看函数中所有变量的值。(使用forEach,您需要更改您所在的堆栈条目以查看循环函数中未访问的变量)
  2. 适用于所有可迭代对象(不仅仅是数组)。
  3. 可以直接使用breakcontinue,而不需要使用some函数和做return true,这样会降低可读性。(可以模仿
  4. 您可以await在循环中使用,并保留 async/await 上下文。(可以模仿
  5. 循环内的代码可以直接返回整个函数之外。(可以模仿

“forEach”的优点

  1. 它是一个类似于mapand的函数调用filter,因此在两者之间进行转换更容易。
  2. 在 es2015 之前的上下文中不需要转译(只需要一个简单的 polyfill)。这也意味着您不必处理来自转译循环的奇怪的“错误捕获”,这使得在开发工具中进行调试变得更加困难。
  3. 您可以制作具有附加功能的“自定义 forEach”方法。是一个增加了对break, continue, return, 甚至async/ 的支持的版本await
  4. 它有点短。for-of如果使用的const多 5 个字符-- 如果使用let/则为3 个var