为什么 JavaScript 没有 Array.prototype.flatMap 方法?

95

flatMap 在集合中非常实用,但是javascript没有提供它,而却有Array.prototype.map。为什么?

在javascript中是否有一种易于且高效的方式来模拟flatMap而不需要手动定义flatMap函数呢?


11
为什么?因为还没有人提出建议?这里是如何提出新功能的方式。 "有没有办法在不手动定义flatMap的情况下模拟它?" 呃?我不明白。你的意思是像arr.reduce((arr, v) => (arr.push(...v), arr), [])这样吗? - Felix Kling
你可以在.map操作后将数组压平 - kennytm
1
嗯,你想要“模拟”它,但不是“定义”它。那可能意味着什么? - user663031
@FelixKling,Brian Terlson的GitHub上曾有一个提案将其添加到JS中,但我现在找不到它了。 - Jared Smith
8
它很快会成为 JS 的一部分。 - Vijaiendran
显示剩余2条评论
8个回答

119

更新:Array.prototype.flatMap已经成为了ES2019的一部分。

它在许多环境中得到广泛支持。请使用下面的代码片段检查它在您的浏览器中是否可用 -

const data =
  [ 1, 2, 3, 4 ]
  
console.log(data.flatMap(x => Array(x).fill(x)))
// [ 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 ]

“为什么 JavaScript 没有 Array.prototype.flatMap 方法?”因为编程不是魔法,每种语言都没有其他语言拥有的特性/原语。重要的是 JavaScript 让你可以自己定义它 -

const concat = (x,y) =>
  x.concat(y)

const flatMap = (f,xs) =>
  xs.map(f).reduce(concat, [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

或者进行一次重写,将这两个循环合并成一个 -

const flatMap = (f, xs) =>
  xs.reduce((r, x) => r.concat(f(x)), [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

如果您希望将其扩展到 Array.prototype,没有任何阻碍 -

if (!Array.prototype.flatMap) {
  function flatMap (f, ctx) {
    return this.reduce
      ( (r, x, i, a) =>
          r.concat(f.call(ctx, x, i, a))
      , []
      )
  }
  Array.prototype.flatMap = flatMap
}

const ranks =
  [ 'J', 'Q', 'K', 'A' ]
  
const suits =
  [ '♡', '♢', '♤', '♧' ]

const result =
  ranks.flatMap(r =>
    suits.flatMap(s =>
      [[r, s]]
    )
  )

console.log(JSON.stringify(result))
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]


1
最好将任何对原型的添加(例如Array.prototype._mylib_flatMap)命名空间化,甚至更好的方法是使用ES6符号,而不是简单地定义Array.prototype.flatMap - Fengyang Wang
13
“被认为更好”是高度主观的。如果这是您自己的应用程序,并且您有理由扩展原型,那么没有问题。如果它是一个您分发给他人使用的库/模块/框架,则我会同意您的看法。然而,评论不是讨论此事的地方 - 此事应提供比评论更详细的解释和细节。 - Mulan
3
关于@SergeyAlaev提到“将地图想象为全局函数”的评论,哈哈,我在许多实用程序函数中确实这样做了。我也使用箭头函数序列对它们进行柯里化。 “丑陋”是主观的。而且你没有提出任何论据支持你的“不好的做法”评论。你的言论表明你对函数式编程的经验很少。拿像Haskell这样的语言,其中预定义了许多“全局变量”,没有人会说它很“丑陋”或者是“不好的做法”。你只是不熟悉它。 - Mulan
5
考虑使用 Racket 这个编程语言,它在默认命名空间中有 appendmapfilterfoldlfoldr 等无数实用函数。别因为听说“全局变量不好”或“扩展内置函数不好”就认为这种做法不好。Racket 是最优秀的语言之一,只是你需要知道什么时候以及如何恰当地使用它们。 - Mulan
1
@elliott-jones 喜欢主观/条件的区分。 - JoeCool-Avismón
显示剩余5条评论

52

flatMap 已经通过 ES2019(ES10)的 TC39 标准审查。您可以像这样使用它:

[1, 3].flatMap(x => [x, x + 1]) // > [1, 2, 3, 4]

这是我自己实现的方法:

const flatMap = (f, arr) => arr.reduce((x, y) => [...x, ...f(y)], [])

flatMap 的 MDN 文章


特别是,根据 MDN,flapMap 在 Node 11.0.0 中已正式推出:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap#Browser_compatibility。 - Carl Walsh
8
抱歉,我忍不住了。flapMap是什么意思?我想要展开我的地图,而不是拍打它! - ErikE
1
另外一个话题是,这最终将JavaScript数组转换为单子™️。 - Kutyel

11

我知道你说你不想亲自定义它,但是这个实现是一个相当简单的定义。

这个github页面还有这个:

这里有一种使用es6展开符号的更短的方法,类似于renaudtertrais的方法-但是使用es6并且没有添加到原型中。

var flatMap = (a, cb) => [].concat(...a.map(cb))

const s = (v) => v.split(',')
const arr = ['cat,dog', 'fish,bird']

flatMap(arr, s)

这两个方案都有帮助吗?

需要注意的是(感谢@ftor),如果在一个非常大的数组(例如300k个元素)a上调用后面的“解决方案”,会出现“超过最大调用堆栈大小”的错误。


2
这个实现可能会在大数组上导致栈溢出。 - user6445533
只是为了明确,如果我理解你的意思正确的话,@ftor,你所说的是我给出链接中的递归实现,而不是我在答案末尾显示的代码块中的实现,对吗? - Alan Mimms
2
尝试使用 [].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase())))。当然,这只是一个极端情况。但你至少应该提到它。 - user6445533
谢谢。我已经添加了一条相关的注释。 - Alan Mimms

7

Lodash 提供了一个 flatmap 函数,对我来说它几乎等同于 JavaScript 原生提供的该函数。如果你不是 Lodash 用户,则可以使用 ES6 的 Array.reduce() 方法获得相同的结果,但需要分步完成映射和展平操作。

以下是每种方法的示例,映射整数列表并仅返回奇数。

Lodash:

_.flatMap([1,2,3,4,5], i => i%2 !== 0 ? [i] : [])

ES6 Reduce:

[1,2,3,4,5].map(i => i%2 !== 0 ? [i] : []).reduce( (a,b) => a.concat(b), [] )

4

一个相对简洁的方法是利用Array#concat.apply

const flatMap = (arr, f) => [].concat.apply([], arr.map(f))

console.log(flatMap([1, 2, 3], el => [el, el * el]));


1

现在Javascript中有一个flatMap()方法!而且它被很好地支持。

flatMap()方法首先使用映射函数映射每个元素,然后将结果平铺到一个新数组中。它与深度为1的map()和flat()组合相同。

const dublicate = x => [x, x];

console.log([1, 2, 3].flatMap(dublicate))


运行发布的代码时出现错误 { "message": "Uncaught TypeError: [1,2,3].flatMap is not a function", "filename": "https://stacksnippets.net/js", "lineno": 15, "colno": 23 } - SolutionMill
你正在使用什么浏览器?Edge、IE和三星Internet目前还不支持它,但Edge即将在V8上运行。因此,为了支持这些浏览器,您可以使用一个polyfill。 - bigInt
Chrome 版本 67.0.3396.87(官方版本)(64 位) - SolutionMill
您的浏览器已经过时了。您应该更新它或将其设置为自动更新。 - bigInt

1
我做了类似这样的事情:
Array.prototype.flatMap = function(selector){ 
  return this.reduce((prev, next) => 
    (/*first*/ selector(prev) || /*all after first*/ prev).concat(selector(next))) 
}

[[1,2,3],[4,5,6],[7,8,9]].flatMap(i => i); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

[{subarr:[1,2,3]},{subarr:[4,5,6]},{subarr:[7,8,9]}].flatMap(i => i.subarr); //[1, 2, 3, 4, 5, 6, 7, 8, 9]


嗨serhii, 我已经尝试了这段代码,当父对象只有一个时,会给出意外的结果。Array.prototype.flatMap = function(selector){ if (this.length == 1) { return selector(this[0]); } return this.reduce((prev, next) => (/*第一个*/ selector(prev) || /*除第一个之外的所有*/ prev).concat(selector(next))) }; - Johnny

0

Array.prototype.flatMap()现在已经出现在JS中。然而,并非所有浏览器都支持它们,请检查当前浏览器的兼容性Mozilla web docs

flatMap()方法的作用是首先使用回调函数作为参数映射每个元素,然后将结果展平为一个新数组(2D数组现在变成了1D,因为元素被展平了)。

这里还有一个如何使用该函数的示例:

let arr = [[2], [4], [6], [8]]

let newArr = arr.flatMap(x => [x * 2]);

console.log(newArr);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接