原文:https://hackernoon.com/rethinking-javascript-break-is-the-goto-of-loops-51b27b1c85f8#.k2oyppp5i
在我的上一篇文章 Death of the for Loop中,我试图去说服你放弃使用for
循环改用函数式的解决方案。反过来,你提出了一个很好的问题,那么for循环中break
怎么办?
break 会相当于循环中的
GOTO
,我们应该避免使用。
break
应该像GOTO
一样被废弃。
你可能会想“算了吧Joel,你这只是耸人听闻,break怎么可能会像GOTO
一样?”
// bad code. no copy paste.
outer:
for (var i in outerList) {
inner:
for (var j in innerList) {
break outer;
}
}
我可以提供标记作为证明。在其他语言中,标记和GOTO
是相互对应的。在JavaScript,标记与break
和continue
也是相互对应的。因为break
和continue
来自于相同标记组,这也导致了它们和GOTO
很像。
JavaScript标签,break和continue是GOTO和非结构化编程时代的遗留
“但是如果它没有伤害任何人,那么我们为什么不把它留下语法中,而我们可以选择其他的方案?”
我们为什么限制我们如何编写软件?
这个听上去有些违背直觉,但是限制是一个好事。限制我们使用GOTO
就是一个很好的例子。我们也很欢迎限制我们的“use strict”
,甚至批评不使用它的人。
“limitations can make things better. A lot better. “— Charles Scalfani
限制(规则)可以使我们写出更好的代码。
为什么编程需要限制
_限制使艺术,设计,生活更美好._medium.com
我们对于break的选择是什么?
我不是要做一个虚有其表的事情,但是也没有一个方案可以适合所有的情况。 这是一个完全不同的编程方式。 一个完全不同的思考方式。函数式编程的思想。
有一个好消息是,有很多的库和工具可以帮助我们,例如Lodash, Ramda, lazy.js, 递归等等
我们将从一个简单的cats集合和一个isKitten
函数开始,这些将在下面所有的例子中被用到。
const cats = [
{ name: 'Mojo', months: 84 },
{ name: 'Mao-Mao', months: 34 },
{ name: 'Waffles', months: 4 },
{ name: 'Pickles', months: 6 }
]
const isKitten = cat => cat.months < 7
让我们从一个我们熟悉的for
循环的例子开始。它会遍历我们的cats,然后当找到第一只小猫的时候退出循环。
var firstKitten
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
firstKitten = cats[i]
break
}
}
现在,让我们和lodash中一个相同作用的例子做比较。
const firstKitten = _.find(cats, isKitten)
这个例子相当的简单。接下来让我们尝试一些边缘情况吧。现在我们改为遍历cat集合,然后选出前5只小猫,然后退出循环。
var first5Kittens = []
// old-school edge case kitty loop
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
first5Kittens.push(cats[i])
if (first4Kittens.length >= 5) {
break
}
}
}
简单的方式
lodash是一个很好的库也可以坐很多的事情,但是有时候你需要一些其他更加专业的工具。这里我们介绍一个新朋友, lazy.js. “像Underscore,但是更加偷懒”。但是偷懒就是我们想要的.
const result = Lazy(cats)
.filter(isKitten)
.take(5)
困难的方法
库都是有趣的,但是有时候真正有趣的是从头开始创造东西。
所以我们可以创建一个通用的函数,让它可以像filter
一样使用也可以增加限制的功能。
第一步就是把我们上面写的边缘情况的for循环封装在一个函数中。
接下来,让我们是这个函数更加通用并且遍历所有cat具体的内容。使用limit
来代替5
,predicate
来代替isKitten
,list
来代替cats
。然后把这些作为函数的参数。
现在我们有了一个可用的且可重复的takeFirst
函数,这个可以让我们完全不用去关心我们cat的逻辑实现!
我们的函数现在依旧还是一个纯函数。也就是说函数的输出只和输入的参数有关。如果传入相同的参数,一定会得到相同的结果。
现在我们已经还是有那个肮脏的for
循环,所以让我们继续重构。下一步就是把i
和newList
放入参数列表。
当limit
变为0的时候 (limit
会在递归过程中减少)或者是遍历完了列表,我们希望可以退出递归(isDone
)。
如果递归还在进行,我们将会核对是否有符合我们的过滤条件predicate
的值。如果当前值符合过滤条件,我们会调用takeFirst
,减少limit
并把当前值保存在我们的newList
中,否者,移动到列表的下一个值。
如果你还没有看过Rethinking JavaScript: The if statement,它会解释这个用三元表达式代替if
'的最后一步。
Rethinking JavaScript: The if statement
_Thinking functionally has opened my mind about programming._medium.com
现在我们像下面这样调用我们的新方法:
const first5Kittens = takeFirst(5, isKitten, cats)
为了兼容更多的情况,我们可以柯里化takeFirst
,然后使用它去创建一些其他的函数(关于柯里化的介绍在另一篇文章中)
const first5 = takeFirst(5)
const getFirst5Kittens = first5(isKitten)
const first5Kittens = getFirst5Kittens(cats)
总结
现在有很多优秀的库例如 lodash, ramda和lazy.js供我们使用。但是如果我们足够大胆,也可以使用递归来创建我们自己的函方法。
我必须要警告虽然takeFirst
看上去很酷 但是使用递归是有得也有失的. 递归在Javascript中是很危险的,它很容易就会导致超出最大调用堆栈大小
的报错。
我将会在我的下一篇文章中重写JavaScript的递归。敬请关注。
我知道这只是一件小事,但是当我收到来自Medium和Twitter (@joelnet)的follow通知时会使我很开心。当然,如果你觉得我实在胡说八道,你也可以在下面的讨论区告诉我。
Cheers!