我今天了解到forEach()
返回undefined
。多么浪费!
如果它返回原始数组,那么在不破坏任何现有代码的情况下它将更加灵活。是否有任何理由forEach
返回undefined
。
无论如何将forEach
链接到其他方法,例如map
& filter
?
例如:
var obj = someThing.keys()
.filter(someFilter)
.forEach(passToAnotherObject)
.map(transformKeys)
.reduce(reduction)
无法正常工作,因为forEach
并不想玩得很好,要求您再次在forEach
之前运行所有方法,以使对象处于所需的状态forEach
。
答案 0 :(得分:22)
您希望通过method cascading将其称为method chaining。简要描述一下:
方法链接是指方法返回的对象具有您立即调用的另一个方法。例如,使用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
等可级联函数(如果它是可级联的)混合在一起是亵渎神灵,因为它会在其他方面引入副作用转化
这是明确的。正如The Zen of 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
方法都是必不可少的,需要额外的工作来理解每个函数的作用,自己弄清楚这些额外的{{1} }方法对于计算forEach
并不是必不可少的,将它们从代码的心理模型中消除,只关注基本部分。
您的程序中添加了许多不必要的复杂性,并且您认为它使您的程序更加简单。
答案 1 :(得分:1)
很容易实现可链接的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]