三个比它们等效 ES5 速度慢的 ES 6 函数

简介: 我在 medium 上看到一篇 3 JavaScript Performance Mistakes You Should Stop Doing 文章(点击阅读全文可以查看原文,需要科学上网),大概意思就是说有 3 个 JavaScript 性能错误,你不应该再去写了。

640 (2).jpg

我在 medium 上看到一篇 3 JavaScript Performance Mistakes You Should Stop Doing 文章(点击阅读全文可以查看原文,需要科学上网),大概意思就是说有 3 个 JavaScript 性能错误,你不应该再去写了


文章主要内容:


当 ES5 发布的时候,JavaScript 引入了很多新的数组函数。其中包括 forEach,reduce,map,filter - 它们让我们感觉语言在不断增长,功能越来越强大,编写代码变得更加有趣和流畅,结果更易于阅读和理解。


大约在同一时间,一个新的环境--Node.js,它使我们能够从前端到后端平稳过渡,同时真正重新定义完整的全栈开发。


所以作者就测试了一下新提供的这些方法是否会影响我们程序的性能。他在 macOS 上对Node.js v10.11.0 和 Chrome 浏览器执行了以下测试。


1. 循环数组


他想到的一个很常见的场景,就是计算一下 10k 项的总和。然后比较了使用 for,for of,while,forEach 和 reduce 的随机 10k 项的总和。运行测试 10,000 次返回以下结果:


For Loop, average loop time: ~10 microseconds
For-Of, average loop time: ~110 microseconds
ForEach, average loop time: ~77 microseconds
While, average loop time: ~11 microseconds
Reduce, average loop time: ~113 microseconds


在谷歌搜索如何对数组求和时,reduce 是最好的解决方案,但它是最慢的。即使是最新的(ES6)也提供了较差的性能。事实证明,老的 for 循环提供了迄今为止最好的性能 - 超过 10 倍以上!


最新推荐的解决方案如何使 JavaScript 变得如此之慢?造成这种痛苦的原因有两个主要原因:reduce 和 forEach 需要执行一个回调函数,这个函数被递归调用并使堆栈膨胀,以及对执行代码进行的附加操作和验证(在此描述 https://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21)。


2. 复制数组


虽然这听起来不那么有趣,但这是不可变函数的支柱,它在生成输出时不会修改输入。

此处的性能测试结果再次显示了同样有趣的趋势 - 当重复 10k 随机项的 10k 数组时,使用旧的传统解决方案更快。同样最新的 ES6 扩展操作符 [... arr] 和来自 Array.from(arr) 的数组加上 ES5  的 map arr.map(x => x)  不如老的 slice arr.slice() 和 concat [] .concat(arr)


Duplicate using Slice, average: ~367 microseconds
Duplicate using Map, average: ~469 microseconds
Duplicate using Spread, average: ~512 microseconds
Duplicate using Conct, average: ~366 microseconds
Duplicate using Array From, average: ~1,436 microseconds
Duplicate manually, average: ~412 microseconds


3. 迭代对象


另一种常见的情况是迭代对象,当我们尝试遍历 JSON 和对象时,这是必要的,而不是寻找特定的键值。同样有老 的解决方案,如 for-in for(let key in obj),或者后来的 Object.keys(obj)(在 es6 中显示)和 Object.entries(obj) (来自ES8)它返回 keys 和 vaues。


使用上述方法对 10k 个对象迭代进行性能分析,每个迭代包含 1,000 个随机 key 和value,得到以下结论。


Object iterate For-In, average: ~240 microseconds
Object iterate Keys For Each, average: ~294 microseconds
Object iterate Entries For-Of, average: ~535 microseconds


原因是在后两个解决方案中创建了可枚举值数组,而不是在没有 keys 数组的情况下直接遍历对象。但最终结果仍然引起关注。


最后


我的结论很清楚 - 如果快速性能对您的应用程序至关重要,或者您的服务器需要处理一些负载 - 使用最酷,更易读,更感觉的解决方案将会对您的应用程序性能产生重大影响 - 最多可以达到慢 10 倍!


下一次,在盲目采用最新趋势之前,确保它们也符合您的要求 - 对于小型应用程序,快速编写和更易读的代码是完美的 - 但对于压力大的服务器和大型客户端应用程序,这可能不是最好的做法。


评论吃瓜


评论 1


一个英文名叫 Christian Gitter 评论说:


不同意。谁在乎微秒?


如果您正在开发一个高性能的超级关键服务器应用程序,那么您要么首先不使用  JavaScript,要么您将成为一名经验丰富的开发人员,他知道自己在做什么以及谁不仅仅取得他的第一个结果。 “如何将数组相加”,Google 搜索结果并将其作为目标。


我们假设你有一个你注意到的服务很慢。你有两个选择。选项 1 占用了团队中的一个或几个开发人员,让他们花一些时间来优化代码以提高速度。选项 2 正在投入一些资金来扩展您的硬件。我说几乎总是选择 2。


在短期内,让您的开发人员进行优化工作可能比扩展服务器所需的成本更高。长期成本甚至更高,因为您将不得不继续进行这种优化,并且您将失去代码可读性,因此新开发人员需要更长时间来确定代码的作用。


这是你几乎应该做的事情:


  • 循环数组=> [].forEach(…)


  • 复制数组=> const newArray = [… oldArray]


  • 迭代对象


…如果你只需要 values => Object.values(obj).forEach()


…或者如果你想要 keys => Object.entries(obj).forEach()


…或者只是 keys => Object.keys(obj).forEach()


我想你明白我的意思。


作者回复:


“谁在乎微秒?” - 好吧,在我工作的地方,我们每天处理大约550亿个事件,这意味着每秒大约700k个事件,当我们尝试在这种环境中运行节点时……你知道其余的事情。


Stephen Young 回复作者


好吧,让我们对每秒 700k 事件进行数学计算。让我们说,为了论证,20% 的事件(每秒 140k)正在进行一些繁重的工作并循环超过一万件事情。现在,假设您将这些循环从 forEach 优化到 for 循环。您的“基准”可为此更改节省 67 微秒。700k * 0.20 * 67 等于 938 万微秒。这归结为节省了惊人的 9.38 秒。这些秒不是线性的,因为我假设您没有在单个 JavaScript 线程上使用单个服务器消耗 700k 事件。在那种规模上,你并行运行多个线程。所以在这个非常慷慨的例子中,你每秒循环 10k 项、 140k次……你最终可能节省不到一秒钟。


评论 2


对于真正的应用程序性能,整个博客文章遗憾地是非常糟糕的建议。


在优化性能时应该做的第一件事是找到应用程序的实际瓶颈。否则,花费时间来优化对实际执行时间没有实际影响的代码。我是一名软件架构师,我最喜欢的一件事就是让代码快速发展。根据我的经验,主要的瓶颈主要是算法复杂性差。除此之外,算法中经常出现错误,并且在实现中存在许多奇怪之处。所以请使用 https://clinicjs.org/ 等工具。这有助于找到应该优化的代码。


这篇文章中提到的优化是微优化,降低了代码的可读性,因此代码需要更多的时间来阅读和理解,这导致优化热代码路径的时间更短。性能也只是当前版本的快照,并且由于新的引擎优化,相同的代码在下一版本中可能表现得非常不同。


如果内置函数确实比不同的实现慢得多(由于 V8 团队很厉害,这种情况不再那么常见),请向 V8 团队报告,以便他们可以进一步优化这些部分。还要注意,由于底层引擎优化(死代码消除等),基准测试本身可能不会按预期运行。


个人看法


首先发布个人看法,我觉得这篇文章还是很有价值的,很有趣的,它带给我们的价值不是说这些比较的数字比较有价值,而是另外的两点:


  1. 让我们要注意,google 出来的第一个答案不一定是好的答案。


  1. ES 新发布的特性不一定是最好的解决方案,我们需要引起注意。


  1. 当发现性能瓶颈的时候这也许是个方案(但是依我看来大多数用不上)


另外我观看评论得出以下结论:


  1. 过早优化是万恶之源,代码的可读性比性能更重要,因为代码是写来给人看的,不是给机器看的。这点让我想到前几天在掘金里面争论 FP(函数式编程) 和 OOP(面向对象编程)哪个好,或者说该用哪个?我的观点就是看自己的团队,如果你的团队大部分人都喜欢 FP,都熟悉 FP,大家都看得懂 FP,那么就可以用 FP,比如 Facebook 的 React 团队,他们很多都热衷于 FP,那么都是用 FP 有何不可呢?因为 FP 的抽象程度更高,所以对开发者要求相对高一点,所以对于不习惯 FP 的团队,还是老老实实的 OOP 吧。我这里没说 FP 就比 OOP 更好,只是想表达看个人爱好的团队爱好。


  1. 对于作者文中提到的这种微优化,在绝大部分情况下是没必要的,性能瓶颈往往可以通过优化算法来解决,算法解决不了可以通过增加服务器来解决。(所以这个观点我跟大部分的评论者相同)
目录
相关文章
|
30天前
|
JavaScript 前端开发 Android开发
转换 ES6 代码时需要注意哪些兼容性问题
在转换ES6代码时,需关注兼容性问题,如箭头函数、模板字符串、let/const等语法在旧浏览器中的支持情况,以及模块化、类、Promise等特性是否需要polyfill。使用Babel等工具可有效解决大部分兼容性问题。
|
1月前
|
前端开发 网络架构
ES6对函数做了哪些扩展?
本文首发于微信公众号“前端徐徐”,介绍了 ES6 中函数参数的默认值、rest 参数、严格模式、name 属性、箭头函数、尾调用优化等新特性,并详细解释了各个特性的使用方法和注意事项。同时,还介绍了 ES2017 和 ES2019 中关于函数的一些改进,如函数参数尾逗号、`Function.prototype.toString()` 方法的修改以及 `catch` 语句参数的省略。
12 1
|
6月前
|
JavaScript 前端开发
CMD和UMD,ES Module的差别
CMD和UMD,ES Module的差别
|
6月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的Generator函数,以及它的用途。
ES6的Generator函数是暂停/恢复功能的特殊函数,利用yield返回多个值,适用于异步编程和流处理,解决了回调地狱问题。例如,一个简单的Generator函数可以这样表示: ```javascript function* generator() { yield 'Hello'; yield 'World'; } ``` 创建实例后,通过`.next()`逐次输出"Hello"和"World",展示其暂停和恢复的特性。
40 0
|
6月前
|
JavaScript 前端开发
ES6 函数
ES6(ECMAScript 2015)是 JavaScript 的一个重要版本,它引入了许多新的特性和语法。其中,函数是 ES6 的一个重要组成部分,它提供了许多新的函数语法和特性,如箭头函数、函数参数默认值、函数解构赋值等。
41 8
|
JavaScript 前端开发 安全
ES6(数值的扩展)
ES6(数值的扩展)
66 0
|
11月前
|
网络架构
ES6学习(六)—函数的扩展
ES6学习(六)—函数的扩展
|
JavaScript 索引
【ES6】语法变化
【ES6】语法变化
106 0
|
JavaScript 前端开发 编译器
CommonJS与ES6 Module的本质区别
文章主要讨论了CommonJS和ES6 Module两种JavaScript模块系统的核心区别,包括动态与静态解决依赖方式,值拷贝与动态映射,以及如何处理循环依赖的问题。
224 0