内容简介:终于我还是单独写一篇文章来说明不写 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 循环》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Oricle8i Web开发指南
(美)Bradley D.Brown / 机械工业出版社 / 2001-6 / 78.00元
本书用实际通用的策略,阐明了怎样一起来看看 《Oricle8i Web开发指南》 这本书的介绍吧!