03 April 2013

原文:ECMAScript 6 and Array Comprehension

很多现代编程语言支持列表推导式,一种基于另一个列表创建列表的简洁方式,另一个列表中的每个条目是某些操作的结果。如果适当的使用推导式,将消除传统的、容易出错的手动迭代的需求。下一代 JavaScript 通过数组推导式提供类似的功能。

首先,让我们快速回顾一下 Array 的 mapfilter 方法。

Array.prototype.map

官网的 ECMAScript 5.1 规范15.4.4.19 节定义了 Array.prototype.map 的官方行为。这个函数返回一个新数组,针对每个条件应用给定的回调函数,新数组由回调函数的返回值组成。

两个简单的例子:

[1, 2, 3].map(function (i) { return i * i }); // [1, 4, 9]
[650,123,4567].map(String).join('-'); // "650-123-4567"

用单行代码创建一组数字序列:

Array.apply(0, Array(3)).map(function(x, y) { return y }); // [0, 1, 2]

甚至是英文字母表“ABCDEFGHIJKLMNOPQRSTUVWXYZ”:

Array.apply(0, Array(26)).map(function(x,y) {
    return String.fromCharCode(y + 65);
}).join('');

对于其他的变种,查看 Brandon Benvie 的 apply-map 用法和 Ben Alman 的 Object.keys 技术。

Array.prototype.filter

官方的 ECMAScript 5.1 的 15.4.4.20 节定义了 Array.prototype.filter 的官方行为。正如名字所示,这个函数使你可以按照一定的条件包括或排除数组中某些条目。来看看下面的例子:

[1,4,2,3,-8].filter(function(i) { return i < 3 }); // [1, 2, -8]

让我们扩展之前的数字序列生成过程,只含有奇数:

Array.apply(0, Array(6)).map(function(x,y) { return y }).
filter(function(x,y) { return y & 1 }); // [1, 3, 5]

我们也可以做一个复杂的处理来打印除了元音之外的所有辅音:

Array.apply(0, Array(26)).map(function(x,y) { return String.fromCharCode(y + 65) }).
filter(function(s) { return 'AEUIO'.indexOf(s) < 0 }).join('');

真实世界的应用程序可能比上面的代码片段更实用。它可能是这样的:

  • 给出某个邮政编码地区的房价列表
  • 工程部门的总开支是多少?
  • 找出 X 一代薪酬最高的职业

译注:Generation X n. 无名一代;X一代;被遗忘的一代(出生于1970年代的美国人)

数组推导式

数组推导式是一个语法特性,Firefox 支持它已经有一段时间了。然而它不是 ECMAScript 5 的一部分,因此没有其他的浏览器支持它。好消息是,数组推导式被并入下一个 ECMAScript 6。最新的 2012/12/21 草稿的 11.1.4.2 节 包含了数组推导式。

理解数组推导式是如何工作的一个简单方法是,把它与 map 和 filter 比较。参阅下面的两个代码,它们给出完全相同的结果。其中第二行在前面的 map 示例中已经看到过。

[i * i for i of [1, 2, 3]]; // [1, 4, 9]
[1, 2, 3].map(function (i) { return i * i }); // [1, 4, 9]

当你使用两个或更多个 foo 之句时,事情变得有趣起来。下面这行代码创建一个列表,包含了棋盘从“A1”到“H8”的所有 64 个可能的方块。

[(x+y) for x of 'abcdefgh'.split('') for y of '12345678'.split('')];

如果看起来仍然很混乱,我非常推荐理解它的语法树,例如通过使用 Esprima 的可视化语法

es6comprehension

注: ES6 和 Firefox 之间有微小语法差异,就是 ES6 中的 for 子句不使用括号。Firefox 的数组推导式还支持 for-in 形式(我不知道是否会进入 ES6)。它可以简化某些构造过程,例如要产生一组数字序列,可以改写为 [j for (j in Array.apply(0, Array(3)))]

使用数组推导式执行过滤是简单的。在这里再次比较两种不同的形式:

[i for i of [1,4,2,3,-8] if (i < 3)]; 
[1,4,2,3,-8].filter(function(i) { return i < 3 }); // [1, 2, -8]

以及简化字母表序列的打印:

[String.fromCharCode(65 + i) for i of  Array.apply(0, Array(26)).
map(function(x, y) { return y; })].join('');

以及只包含辅音:

[j for j of [String.fromCharCode(65 + i)
for i of Array.apply(0, Array(26)).map(function(x, y) { return y; })]
if ('AEUIO'.indexOf(j) < 0)].join('');

分析下面的表达式作为一个练习。它的作用是产生小于 100 的素数列表。可以看到没有必要手动的循环遍历所有数字。请注意,由于我们是在谈论下一代 JavaScript,我们还可以使用 Arrow Function(13.2 节)来缩短代码。

译注:incantation n. 咒语

[x
  for x of Array.apply(0, Array(99)).map((x, y) => y + 2)
  if [(x % i)
     for i of Array.apply(0, Array(1 + Math.round(Math.sqrt(x)))).map((x, y) => y)
     if ((i > 1) && ((x % i) === 0))
  ].length === 0
];

With the support for array comprehension, JavaScript is getting more and more functional. If you come from Python, Haskell, Scala, or another modern language, you won't feel so powerless anymore!

随着对数组推导式的支持,JavaScript 具备越来越多的功能。如果你来自 Python、Hashhell、Scala 或其他现代语言,你就不会干到那么无能为力了!



blog comments powered by Disqus