为啥 forEach 比常规迭代器更受欢迎?
Posted
技术标签:
【中文标题】为啥 forEach 比常规迭代器更受欢迎?【英文标题】:Why should forEach be preferred over regular iterators?为什么 forEach 比常规迭代器更受欢迎? 【发布时间】:2018-08-31 10:22:32 【问题描述】:我正在阅读airbnb javascript guide。有一个特别的说法,说:
不要使用迭代器。更喜欢 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
有什么副作用吗?
【问题讨论】:
更具可读性?更容易组合? 你会感到惊讶。 For 循环并不容易学习。 Foreach 是。 【参考方案1】:Airbnb 风格指南中的这个推理适用于用于不可变性的数组方法,即filter
、map
、reduce
等,但不适用于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 中显着改进的那个(几乎是两倍,基于 this 综合测试):
由于它们都很快,因此仅出于性能原因选择不太合适的循环方法可以被视为初步优化,除非另有证明。
forEach
与for..of
相比的主要优点是前者即使在 ES3 中也可以进行 polyfill 并提供值和索引,而后者更具可读性但应该在 ES5 及更低版本中进行转译。
forEach
存在已知的陷阱,使其在某些情况下不适合使用 for
或 for..of
进行妥善处理:
回调函数创建新上下文(可以用箭头函数解决)
不支持迭代器
不支持生成器yield
和async..await
没有提供正确的方法来使用break
提前终止循环
【讨论】:
我没看懂开头行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
不能用于不变性?
可以的。但它本质上不是一成不变的。不会自动为您提供新值(例如 reduce
或 map
)。您在其他地方定义的循环内改变了一个变量(在您的示例中为sum
)。您可以以相同的方式修改现有对象,从而打破“不可变规则”(好吧,您也可以使用 reduce
来做到这一点,但这通常被认为是一个错误)。 for
和 forEach
同样是“坏”的,根据这个推理,forEach
有一个函数这一事实在这方面不会影响任何事情,因为我们不能从这个函数返回结果。跨度>
我认为这个推理是有效的(尽管通常更喜欢for..of
以提高可读性),但不是他们得出的结论。 github.com/airbnb/javascript#iterators-and-generators章节自相矛盾,前后矛盾。在一个地方,它声明应该避免副作用,然后他们在 forEach 中产生副作用并将其标记为“好”。请记住,这只是风格指南。风格指南是固执己见的,并不总是有意义。【参考方案2】:
这没有任何意义。 forEach
永远不应该被首选。是的,使用 map
和 reduce
和 filter
比使用副作用来操作某些东西的循环要干净得多。
但是当你出于某种原因需要使用副作用时,for … in
和 for … of
是惯用的循环结构。它们更容易阅读和just as fast,并强调你有一个带有副作用的循环体,而forEach
看起来可以通过它的回调来运行,但实际上并没有。与forEach
相比的其他优点包括您可以将const
用于迭代元素,并且可以在循环体中使用任意控制结构(return
、break
、continue
、yield
、await
) .
【讨论】:
n.b. Eric Lippert 在 C# 中对foreach
与 .ForEach(...)
的基本相同:blogs.msdn.microsoft.com/ericlippert/2009/05/18/…
【参考方案3】:
大部分时间,Airbnb 风格指南都试图保持一致。这并不意味着永远没有理由使用 for 循环或 for-in 循环,比如假设您想尽早跳出循环。
当使用 for 循环改变某物的值时,不变性就发挥了作用。就像将数组简化为整数或映射元素以生成新数组一样。使用内置的map
、reduce
或filter
不会直接直接改变数组的值,因此它是首选。在 for/for-in 循环上使用 forEach
可以强制在迭代器上使用高阶函数的样式一致性,所以我相信这就是推荐它的原因。
【讨论】:
您能解释一下This isn't the same for forEach
的声明吗?与map
、reduce
等其他方法相比,为什么forEach
会改变数组的值?
forEach
本身不会改变数组的值。我的意思是,与map
或filter
不同,forEach
将始终返回undefined
,它没有返回值并且可以用来改变数组(应该更好地澄清这一点)。已更新。【参考方案4】:
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]
数组的元素每两秒打印一次。
[a,b,c]
数组的元素在两秒后一起打印。
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]
数组的元素每两秒打印一次。
尝试访问asyncIterator
的[a,b,c]
数组上的forEach
时出错。
【讨论】:
【参考方案5】:以下是我看到的两者之间的优势列表。
“for of”的优点:
-
在开发工具中,你可以通过鼠标悬停查看函数中所有变量的值。 (使用
forEach
,您需要更改您所在的堆栈条目以查看未在循环函数中访问的变量)
适用于所有可迭代对象(不仅仅是数组)。
你可以直接使用break
或continue
,而不需要使用some
函数和return true
,这样会降低可读性。 (可以是emulated)
您可以在循环中使用await
,同时保留异步/等待上下文。 (可以是emulated)
循环内的代码可以直接从整个函数中返回。 (可以是emulated)
“forEach”的优点
-
是像
map
和filter
这样的函数调用,所以更容易在两者之间转换。
在 es2015 之前的上下文中使用它不需要转译(只需要一个简单的 polyfill)。这也意味着您不必处理来自转译循环的奇怪“错误捕获”,这会使开发工具中的调试变得更加困难。
您可以创建具有附加功能的“自定义 forEach”方法。 (here 是增加了对break
、continue
、return
甚至async
/await
的支持的版本)
有点短。 (如果使用const
,for-of
多 5 个字符 -- 如果使用 let
/var
,则多 3 个字符)
【讨论】:
【参考方案6】:在此处查看Performance of foreach and forloop
我们使用 foreach 只是为了可读性。 除了浏览器兼容性、性能和原生感觉,更喜欢 for 循环。
【讨论】:
您提供的链接表明forEach
要快得多。
“没有区别” - 他说的是 V8 引擎。这就是区别。在 safari 上 for
始终比 foreach
快
@Bergi 有区别。 AIK,for > forEach > for..of > for..in 就性能而言,这在几乎所有引擎中都是统一的。仍然没有奇偶校验,您可以自己查看jsperf.com/for-vs-foreach-vs-for-of。有趣的是,forEach 在最新的 Chrome 版本中得到了显着改进。以上是关于为啥 forEach 比常规迭代器更受欢迎?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 NodeJs 开发中 Mongodb 比 MySql 更受欢迎? [关闭]