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

Jon*_*ine 15 javascript arrays functional-programming

我今天学到了forEach()回报undefined.多么浪费!

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

有没有forEach与其他方法链接,如map&filter

例如:

var obj = someThing.keys()
.filter(someFilter)
.forEach(passToAnotherObject)
.map(transformKeys)
.reduce(reduction)
Run Code Online (Sandbox Code Playgroud)

不行,因为forEach不想玩好,要求你再次运行所有方法,forEach以获得所需的状态对象forEach.

Aad*_*hah 23

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

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

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

    foo
        ..bar()
        ..baz();
    
    Run Code Online (Sandbox Code Playgroud)

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

    foo.bar();
    foo.baz();
    
    Run Code Online (Sandbox Code Playgroud)

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

foo
    .bar()
    .baz();
Run Code Online (Sandbox Code Playgroud)

一些方法喜欢filtermap可链接但不可级联,因为它们返回一个新数组,但不返回原始数组.

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

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

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

arr.forEach(passToAnotherObject);

var obj = arr
    .map(transformKeys)
    .reduce(reduction);
Run Code Online (Sandbox Code Playgroud)

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

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

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

    另一方面,可链接的函数喜欢map并且filter没有任何副作用(如果它们的输入函数没有任何副作用).它们仅用于结果.

    在我的愚见,混合环连接的方法,如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);
    
    Run Code Online (Sandbox Code Playgroud)

    上述程序明确涉及控制流程,因为您明确指出.forEach(passToAnotherObject)应该在之前发生,.map(transformKeys)即使它不会对整体转换产生任何影响.

    事实上,你可以完全从等式中删除它,它不会有任何区别:

    var obj = someThing.keys()
        .filter(someFilter)
        .map(transformKeys)
        .reduce(reduction);
    
    Run Code Online (Sandbox Code Playgroud)

    这表明,首先.forEach(passToAnotherObject)没有任何业务在等式中.由于它是一个有效的操作,它应该与纯代码分开.

    当你像上面那样明确地编写它时,不仅可以将纯代码与副作用代码分开,而且还可以选择何时评估每个计算.例如:

    var arr = someThing.keys()
        .filter(someFilter);
    
    var obj = arr
        .map(transformKeys)
        .reduce(reduction);
    
    arr.forEach(passToAnotherObject); // evaluate after pure computation
    
    Run Code Online (Sandbox Code Playgroud)

    是的,您仍然明确关注控制流程.但是,至少现在你知道这.forEach(passToAnotherObject)与其他转换无关.

    因此,您已经消除了由控制流明确关注引起的一些(但不是全部)复杂性.

由于这些原因,我认为当前的实现forEach实际上是有益的,因为它会阻止您编写由于明确关注控制流而引入复杂性的代码.

我从个人经验中了解到,当我以前在BrowserStack工作时,明确关注控制流是大型软件应用程序中的一个大问题.这确实是一个现实世界的问题.

编写复杂的代码很容易,因为复杂的代码通常是较短(隐式)的代码.因此,forEach在纯计算的中间放置一个副作用函数总是很诱人,因为它需要较少的代码重构.

但是,从长远来看,它会使您的程序更加复杂.想想当你退出你工作的公司并且其他人必须维护你的代码时,几年后会发生什么.您的代码现在看起来像:

var obj = someThing.keys()
    .filter(someFilter)
    .forEach(passToAnotherObject)
    .forEach(doSomething)
    .map(transformKeys)
    .forEach(doSomethingElse)
    .reduce(reduction);
Run Code Online (Sandbox Code Playgroud)

现在,阅读代码的人必须假设forEach链中的所有其他方法都是必不可少的,需要额外的工作来理解每个函数的作用,自己弄清楚这些额外的forEach方法对于计算是不必要的obj,从她的心理中消除它们您的代码模型,只关注基本部分.

这为您的程序添加了许多不必要的复杂性,并且您认为它使您的程序更加简单.