繁体   English   中英

为什么Array.prototype.forEach不能被链接?

[英]Why can Array.prototype.forEach not be chained?

我今天了解到forEach()返回undefined 多么浪费!

如果它返回原始数组,那么在不破坏任何现有代码的情况下它将更加灵活。 没有任何理由forEach返回undefined

无论如何forEach mapfilter等其他方法链接forEach

例如:

var obj = someThing.keys()
.filter(someFilter)
.forEach(passToAnotherObject)
.map(transformKeys)
.reduce(reduction)

无法工作,因为forEach不想玩得很好,要求你再次在forEach之前运行所有方法,以使对象处于forEach所需的状态。

你想要的是通过方法链接的 方法级联 简要描述一下:

  1. 方法链接是指方法返回具有您立即调用的另一个方法的对象。 例如,使用jQuery:

     $("#person") .slideDown("slow") .addClass("grouped") .css("margin-left", "11px"); 
  2. 方法级联是在同一对象上调用多个方法时。 例如,在某些语言中,您可以执行以下操作:

     foo ..bar() ..baz(); 

    这相当于JavaScript中的以下内容:

     foo.bar(); foo.baz(); 

JavaScript没有任何特殊的方法级联语法。 但是,如果第一个方法调用返回this方法,则可以使用方法链接模拟方法级联。 例如,在下面的代码中,如果bar返回this (即foo ),则链接等效于级联:

foo
    .bar()
    .baz();

filtermap这样的方法是可链接的但不能级联,因为它们返回一个新数组,但不返回原始数组。

另一方面, forEach函数不可链接,因为它不返回新对象。 现在,问题是是否forEach应该是级联与否。

目前, forEach不是可级联的。 但是,这不是一个真正的问题,因为您可以简单地将中间数组的结果保存在变量中,并在以后使用它:

var arr = someThing.keys()
    .filter(someFilter);

arr.forEach(passToAnotherObject);

var obj = arr
    .map(transformKeys)
    .reduce(reduction);

是的,这个解决方案看起来比您想要的解决方案更加丑陋。 但是,我喜欢它比你的代码更多,原因有以下几点:

  1. 它是一致的,因为可链接方法不与可级联方法混合。 因此,它促进了编程的功能风格(即没有副作用的编程)。

    级联本质上是一种有效的操作,因为您正在调用方法并忽略结果。 因此,您正在调用该操作的副作用而不是其结果。

    另一方面,像mapfilter这样的可链接函数没有任何副作用(如果它们的输入函数没有任何副作用)。 它们仅用于结果。

    在我的拙见中,将可链接的方法(如mapfilter与可级联的函数(如forEach (如果它是可级联的))混合是亵渎神灵,因为它会在纯粹的转换中引入副作用。

  2. 这是明确的。 正如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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM