为什么要避免写 for 循环

栏目: 编程工具 · 发布时间: 5年前

内容简介:终于我还是单独写一篇文章来说明不写 for 循环的理由了。我在写这是我看到的关注最多的点了。先推荐去 FunFunFunction 看下 MPJ 老师对这个问题怎么说

终于我还是单独写一篇文章来说明不写 for 循环的理由了。

我在写 《如何在 JS 代码中消灭 for 循环》 的时候,以为我所倡导的应该已经是一个共识,但没想到会有这么大争议,甚至有些编程经验丰富的前辈也反对。所以,我觉得还是有必要再说明一下。我没想说服所有人,只是想尽到把问题说清楚的责任。

一,性能问题

这是我看到的关注最多的点了。先推荐去 FunFunFunction 看下 MPJ 老师对这个问题怎么说 Fast code is NOT important . 微观层面的极小性能差异,不会成为你整个性能的应用瓶颈。

还见到有朋友说,V8 引擎对 for 循环有优化。拜托,V8 引擎对高阶函数也有优化啊。就我最近知道的例子,V8 引擎的 Array.prototype.sort 方法的底层实现,以前是在 排序 项少于10个的时候用插入排序,在排序项大于10个的时候用快速排序。就在几个月前,V8 使用了更稳定的 TimSort 算法替代了快排。我不了解 TimSort , 但据说是目前性能最好的排序算法。

如果你给几十上百个元素排序(说实话我也不知道前端排序,需要考虑性能的阈值在哪,不测怎么知道?),一开始就考虑性能不用高阶 sort 函数,而是写个 for 循环实现 TimSort,这不是对开发资源的浪费?

我前段在 Twitter 上看到一个小插曲,一个开发者自己写了个方法(时间久了,我忘了是什么方法),发布到 npm 让人去试用。别人问这个方法原生已经有了,你写这个干嘛?他说这个性能好!结果被人怼 "Use the language!" 这个故事我当时还发在掘金沸点了,见这里。

建议大家关注下 V8 团队,看他们的工程师对 JS 开发者的建议是什么。其实底层引擎和开发社区是一直在良性互动的,引擎团队也想让开发者体验好一点,让自家的产品更有竞争力。比如,React 最近搞出 Time Slicing 技术(Vue 3.0 也会跟进)后,V8 团队就觉得为啥不让浏览器原生支持这个呢?未来的 Chrome 可能会支持 Scheduler API.

性能也不是应用要考虑的唯一因素,如果是的话,那可能大部分应用都要用 WebAssembly 重写了。某些情况下确实需要用到这种极端手段,但大多数时候,可维护性和开发效率,是优先于性能的。

二,指令式 V.S. 声明式

指令式(Imperative)编程就是告诉程序每一步怎么做。比如用 jQuery,告诉某个元素,先左移 10 px,再转个圈,再变个颜色闪几下,然后自己消失吧…… 每一步都要告诉操作对象具体指令是什么。

声明式(Declarative)编程就是告诉程序我想要达到怎样的效果,至于是怎么实现的,其它独立模块已经写好了,组合起来就行了。比如我在这篇文章里用 Rx.js 实现的动画:

const moveDown$ = duration(1500).pipe(
    map(easeInQuint),
    map(distance(700)),
    tap(y => (targetDiv.style.top = y + "px"))
);
复制代码

这里的意思就是,告诉目标元素,在 1500 ms 内,以 easeInQuint 的曲线加速度,向下移动 700 px. 是不是很好懂?我只是说了我要什么,具体怎么做的,都在相应独立模块里面。而这些独立模块写一遍就行了,甚至能跨项目复用。想象一下指令式代码能这么灵活和易读吗?

而 for 循环就是指令式的。指令式代码难伸缩,难复用,而且全是实施细节(implementation details),易读性极差。有相当一部分人说高阶函数易读性差,for 循环易读性好。我个人观点是这些朋友需要锻炼下程序抽象能力,for 循环几乎能做到零抽象,读抽象层次低的指令式代码,当然好懂,但不意味着好读。举个例子:

// 在产品列表中找到相应产品,提取出价格,再把价格格式化
const formalizeData = compose(formatCurrency, pluckPrice, findProduct);

formalizeData(products)
复制代码

compose 里面的独立函数实现细节我就不写了。这种写法优势在哪?首先,这种代码几乎不用注释,从右往左读一遍函数名就知道在干嘛。其次,compose 里面的三个独立函数由于是纯函数,可以在其他地方复用。如果用 for 循环一步到位实现 formalizeData 函数,那就没办法复用了。

仅仅为了性能,代码伸缩性和扩展性都不考虑,实在是舍本逐末。

三,改变数据

for 循环由于太底层了,其设计初衷就是执行作用(effects),用来高效执行底层指令。而开发应用时,对于作用是要严格限制的。让你的程序副作用散布在程序各个角落,很容易造成难以发现的 bug。什么是副作用?在一个函数执行计算时,产生了计算目的之外的行为,即是副作用。比如,有一个由数字组成的数组,你想把每一个数字加一个货币符号,然后你用 for 循环把每一个数组元素加了个 $. 这就是副作用,你本来只想要一个格式化价格组成的数组,结果你把原数据改了。如果用 map,就不会改变原数据,而是返回一个新的符合要求的数据。

你也可以说我在 for 循环开始声明一个空对象/数组,然后往这个空对象/数组里 assign/push,这样确实能避免上面说得到的问题。但这种规约靠自觉,而且指令式代码太自由了,难以保证每个人都清楚自己没引起副作用。

副作用最大的问题是无法组合,所以函数式编程才会从数学里面引入抽象层次那么高的 Monad 来解决这个问题。当然这个扯远了,只是想说计算机科学家和数学家们为了驯服副作用费这么大劲,是有原因的。


以上所述就是小编给大家介绍的《为什么要避免写 for 循环》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

第一行代码:Android(第2版)

第一行代码:Android(第2版)

郭霖 / 人民邮电出版社 / 2016-12-1 / CNY 79.00

本书被广大Android 开发者誉为“Android 学习第一书”。全书系统全面、循序渐进地介绍了Android软件开发的必备知识、经验和技巧。 第2版基于Android 7.0 对第1 版进行了全面更新,将所有知识点都在最新的Android 系统上进行重新适配,使用 全新的Android Studio 开发工具代替之前的Eclipse,并添加了对Material Design、运行时权限、......一起来看看 《第一行代码:Android(第2版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具