[英]Why can Array.prototype.forEach not be chained?
我今天了解到forEach()
返回undefined
。 多么浪费!
如果它返回原始数组,那么在不破坏任何现有代码的情况下它将更加灵活。 没有任何理由forEach
返回undefined
。
无论如何forEach
map
和filter
等其他方法链接forEach
?
例如:
var obj = someThing.keys()
.filter(someFilter)
.forEach(passToAnotherObject)
.map(transformKeys)
.reduce(reduction)
无法工作,因为forEach
不想玩得很好,要求你再次在forEach
之前运行所有方法,以使对象处于forEach
所需的状态。
方法链接是指方法返回具有您立即调用的另一个方法的对象。 例如,使用jQuery:
$("#person") .slideDown("slow") .addClass("grouped") .css("margin-left", "11px");
方法级联是在同一对象上调用多个方法时。 例如,在某些语言中,您可以执行以下操作:
foo ..bar() ..baz();
这相当于JavaScript中的以下内容:
foo.bar(); foo.baz();
JavaScript没有任何特殊的方法级联语法。 但是,如果第一个方法调用返回this
方法,则可以使用方法链接模拟方法级联。 例如,在下面的代码中,如果bar
返回this
(即foo
),则链接等效于级联:
foo
.bar()
.baz();
像filter
和map
这样的方法是可链接的但不能级联,因为它们返回一个新数组,但不返回原始数组。
另一方面, forEach
函数不可链接,因为它不返回新对象。 现在,问题是是否forEach
应该是级联与否。
目前, forEach
不是可级联的。 但是,这不是一个真正的问题,因为您可以简单地将中间数组的结果保存在变量中,并在以后使用它:
var arr = someThing.keys()
.filter(someFilter);
arr.forEach(passToAnotherObject);
var obj = arr
.map(transformKeys)
.reduce(reduction);
是的,这个解决方案看起来比您想要的解决方案更加丑陋。 但是,我喜欢它比你的代码更多,原因有以下几点:
它是一致的,因为可链接方法不与可级联方法混合。 因此,它促进了编程的功能风格(即没有副作用的编程)。
级联本质上是一种有效的操作,因为您正在调用方法并忽略结果。 因此,您正在调用该操作的副作用而不是其结果。
另一方面,像map
和filter
这样的可链接函数没有任何副作用(如果它们的输入函数没有任何副作用)。 它们仅用于结果。
在我的拙见中,将可链接的方法(如map
和filter
与可级联的函数(如forEach
(如果它是可级联的))混合是亵渎神灵,因为它会在纯粹的转换中引入副作用。
这是明确的。 正如Python的禅宗告诉我们的那样,“明确比隐含更好。”方法级联只是语法糖。 这是隐含的,而且需要付出代价。 成本很复杂。
现在,您可能会认为我的代码看起来比您的代码更复杂。 如果是这样,你将以封面来判断这本书。 在他们的着名论文“ Out of the Tar Pit”中 ,作者Ben Moseley和Peter Marks描述了不同类型的软件复杂性。
列表中第二大软件复杂性是由于明确关注控制流而导致的复杂性。 例如:
var obj = someThing.keys() .filter(someFilter) .forEach(passToAnotherObject) .map(transformKeys) .reduce(reduction);
上面的程序明确地关注控制流,因为你明确指出.forEach(passToAnotherObject)
应该在.map(transformKeys)
之前发生,即使它不应该对整个转换产生任何影响。
事实上,你可以完全从等式中删除它,它不会有任何区别:
var obj = someThing.keys() .filter(someFilter) .map(transformKeys) .reduce(reduction);
这表明.forEach(passToAnotherObject)
没有任何业务。 由于它是一个有效的操作,它应该与纯代码分开。
当你像上面那样明确地编写它时,不仅可以将纯代码与副作用代码分开,而且还可以选择何时评估每个计算。 例如:
var arr = someThing.keys() .filter(someFilter); var obj = arr .map(transformKeys) .reduce(reduction); arr.forEach(passToAnotherObject); // evaluate after pure computation
是的,您仍然明确关注控制流程。 但是,至少现在您知道.forEach(passToAnotherObject)
与其他转换无关。
因此,您已经消除了由控制流明确关注引起的一些(但不是全部)复杂性。
出于这些原因,我认为forEach
的当前实现实际上是有益的,因为它会阻止您编写由于明确关注控制流而引入复杂性的代码。
我从个人经验中了解到,当我以前在BrowserStack工作时,明确关注控制流是大型软件应用程序中的一个大问题。 这确实是一个现实世界的问题。
编写复杂的代码很容易,因为复杂的代码通常是较短(隐式)的代码。 因此,在纯计算过程中放入像forEach
这样的副作用函数总是很诱人,因为它需要较少的代码重构。
但是,从长远来看,它会使您的程序更加复杂。 想想当你退出你工作的公司并且其他人必须维护你的代码时,几年后会发生什么。 您的代码现在看起来像:
var obj = someThing.keys()
.filter(someFilter)
.forEach(passToAnotherObject)
.forEach(doSomething)
.map(transformKeys)
.forEach(doSomethingElse)
.reduce(reduction);
现在,阅读代码的人必须假设链中所有额外的forEach
方法都是必不可少的,需要额外的工作来理解每个函数的作用,自己弄清楚这些额外的forEach
方法对于计算obj
并不是必不可少的,消除它们从她的代码的心理模型,只关注基本部分。
这为您的程序添加了许多不必要的复杂性,并且您认为它使您的程序更加简单。
实现可链接的forEach
功能很容易:
Array.prototype.forEachChain = function () { this.forEach(...arguments); return this; }; const arr = [1,2,3,4]; const dbl = (v, i, a) => { a[i] = 2 * v; }; arr.forEachChain(dbl).forEachChain(dbl); console.log(arr); // [4,8,12,16]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.