zhouweicsu

「译」ECMAScript 2016 (ES7) 新特性一览

本文回答了关于下一版 JavaScript(ECMAScript 2016)的以下几个问题:

  • 谁在设计?
  • 如何设计?
  • 会有什么特性?

本文会持续更新以及时反馈最新进展。

1、谁在设计 ECMAScript?

答:TC39 (Technical Committee 39)。

TC39 是推进 JavaScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。会议纪录都可在网上查看,可以让你对 TC39 如何工作有一个清晰的概念。

有时候(实际上本文就如此),你会发现 TC39 会员这个词被用来指代一个人,其实指的是 TC39 会员公司派出的代表。

很有意思的是,TC39 实行的是协商一致的原则:通过一项决议必须得到每一位会员(公司代表)的赞成。

2、如何设计 ECMAScript?

2.1 问题:ECMAScript 2015 (ES6) 这个版本太大了

最近发布的 ECMAScript(ES6)新增内容很多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度如此之大主要有两大原因:

  • 比新版率先完成的特性,必须等待新版的完成才能发布。
  • 那些需要花长时间完成的特性,也顶着很大的压力被纳入这一版本,因为如果推迟到下一版本发布意味着又要等很久,这种特性也会推迟新的发布版本。

因此,从 ECMAScript 2016(ES7)开始,版本发布将会变得更加频繁,每年发布一个新版本,这么一来新增内容也会更小。新版本将会包含每年截止时间之前完成的所有特性。

2.2 解决方案: TC39 设计过程

每个 ECMAScript 特性的建议将会从阶段 0 开始, 然后经过下列几个成熟阶段。其中从一个阶段到下一个阶段必须经过 TC39 的批准。

阶段 0: Strawman 初稿

什么是 Strawman?一个推进 ECMAScript 发展的自由形式的想法。该想法必须由 TC39 的会员提交,如果是非会员则必须注册成为 TC39 贡献者才能提交。

必备条件:文件必须在 TC39 的会议上通过审议(原文),然后才能加入阶段 0 的建议页面

阶段 1:Proposal 建议

什么是 Proposal?一份新特性的正式建议文档。

必备条件:必须确定一位带头人来为负责这份建议。无论是带头人或者联合带头人都必须是 TC39 的会员(原文)。建议要解决的问题必须以简明的文字描述,而解决方案则要给出相应的实例和 API,并详细描述语义及算法。最后,必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。实现方式,polyfills 和 demo 也是需要的。

下一步:通过一个阶段 1 的建议,表明 TC39 愿意研究、讨论并促成该建议。接下来,我们就可以期待该建议的重大改变了。

阶段 2:Draft 草案

什么是 Draft?草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。

必备条件:建议此时必须要附加该特性的语法和语义的正式说明(使用 ECMAScript 标准的形式语言)。说明应该尽可能完善,但可以包含待办事项和占位符。该特性需要两个实验性的实现,其中一个可以在类似 Babel 的转译器(transpiler)中实现。

下一步:从该阶段开始只接受增量调整。

阶段 3:Candidate 候选

什么是 Candidate?候选阶段,建议基本完成,此时将从实现过程和用户使用两方面获取反馈来进一步完善建议。

必备条件:规范文档必须是完整的。指定的评审人(由 TC39 而不是带头人指定)和 ECMAScript 规范的编辑须在规范上签字。还有至少要两个符合规范的实现(不必指定默认实现)。

下一步:此后,只有在实现和使用过程中出现了重大问题才会修改建议。

阶段 4:Finished 完成

什么是 Finished?建议已经准备就绪,可以添加到标准之中。

必备条件:建议进入完成阶段之前需要满足以下几点:

  • Test 262 的验收测试(基本上都是 JavaScript 写的用来验证语言特性的单元测试)。
  • 两个通过测试的符合规范的实现。
  • 特性实现相关的重要实践经验。
  • ECMAScript 规范的编辑在规范文本上的签字。

下一步: 建议将会尽快加入 ECMAScript 规范之中。当规范通过年度审核成为标准,该建议也正式成为标准的一部分。

3、别把它们称做 ECMASript 20xx 特性

综上所述,只有建议进入阶段 4,你才能确定这个特性会被纳入标准之中。因为此时它很有可能被加入到下一版本的 ECMAScript,但也不是 100% 确定(因为有可能会花更长时间)。因此,你不能称这个建议为“ES7 特性”或“ES2016 特性”。所以我喜欢把文章或博客的标题写成这样:

  • “ECMAScript 建议:某特性”。在文章开头说明建议所处的阶段。
  • “ES.阶段 2:某特性”。

如果建议处于阶段 4,我觉得称它 ES20xx 特性是 OK 的,但还是等规范的编辑确认该特性将会加入哪个版本之后才最保险。例如 Object.observe 就是一个已经进入阶段 2 ,却最终被撤回的 ECMAScript 建议。

4、ECMAScript 2016 将会有什么特性?

已被纳入 ES2016 的特性:

以下处于阶段 4 的特性将可能会纳入 ES2016:

  • (暂时没有。)

以下处于阶段 3 的特性也许会纳入 ES2016:

如果你不知道某个建议特性处于哪个阶段,你可以在 ECMA-262 GitHub 库的 readme 中查阅。

5、Array.prototype.includes (Domenic Denicola, Rick Waldron)

数组的 includes 方法有如下签名:

1
Array.prototype.includes(value:任意值): boolean

如果传入的值在当前数组(this)中则返回 true,否则返回 false:

1
2
3
4
> ['a', 'b', 'c'].includes('a')
true
> ['a', 'b', 'c'].includes('d')
false

includes 方法与 indexOf 方法很相似——下面两个表达式是等价的:

1
2
arr.includes(x)
arr.indexOf(x) >= 0

唯一的区别是 includes() 方法能找到 NaN,而 indexOf() 不行:

1
2
3
4
> [NaN].includes(NaN)
true
> [NaN].indexOf(NaN)
-1

includes 不会区分 +0-0 (这也与其他 JavaScript 特性表现一致):

1
2
> [-0].includes(+0)
true

类型数组也有 includes() 方法:

Typed Arrays will also have a method includes():

1
2
let tarr = Uint8Array.of(12, 5, 3);
console.log(tarr.includes(5)); // true

5.1 常见问题

  • 为什么方法取名 includes 而不是 contains
    后者是最初的选择,但在 web 上将会破坏已有的代码(MooTools 在 Array.prototype 上添加了 contains 方法)。

  • 为什么方法取名 includes 而不是 has
    has 通常用于键(Map.prototype.has),includes 通常用于元素(String.prototype.includes)。集合中的元素既可以被看当做 也可被当做 ,所以才有 Set.prototype.has (而不是 includes)。

  • ES6 的 String.prototype.includes 方法可用于字符串,而不能用于字符。这是否和 Array.prototype.includes 不一致?
    如果数组和字符串的 includes 方法是相同的工作机制,那么数组的 includes 方法就应该接受数组,而不是数组元素了。不过这两个 includes 方法都参考了 indexOf 方法;字符一般是特殊情况,而任意长度的字符串则更常见。

5.2 扩展阅读

6、指数运算符(Rick Waldron)

新提出来的特性是将 ** 作为指数操作的中缀运算符:

1
x ** y

与以下表达式运算结果相同:

1
Math.pow(x, y)

示例:

1
2
3
4
5
let squared = 3 ** 2; // 9
let num = 3;
num **= 2;
console.log(num); // 9

扩展阅读:

7、SIMD.JS – SIMD APIs + polyfill (John McCutchan, Peter Jensen, Dan Gohman, Daniel Ehrenberg)

SIMD 意思是“single instruction, multiple data(单指令流多数据流)”,CPU 可以通过单条指令操作一组数据(而不是仅操作单一值),SIMD 指令集最有名的一个例子就是由 Intel 处理器的 SSE(Streaming SIMD Extensions)

下面是简短的示例代码,请参考后面源码获取更多信息。

1
2
3
var a = SIMD.float32x4(1.0, 2.0, 3.0, 4.0);
var b = SIMD.float32x4(5.0, 6.0, 7.0, 8.0);
var c = SIMD.float32x4.add(a,b);

扩展阅读:

8、异步函数(Brian Terlson)

在我介绍异步函数之前,我会先介绍如何通过 Promises 和 generators 来使用看起来同步的代码去执行异步操作。

8.1 用 Promises 和 generators 编写异步代码

对于需要异步地计算一次性结果的函数来说,Promises(ES6 的一部分)现在越来越流行。一个例子就是客户端 fetch API,它是一种替代 XMLHttpRequest 检索文件的方案。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));

co 是一个使用了 Promises 和 generators 让编程风格看起来更像异步的库,但也需要使用类似于前一个例子的风格去编码:

1
2
3
4
5
6
7
8
9
10
const fetchJson = co(function* () {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});

每次回调函数(一个 generator 函数!)产生一个 Promise 给 co,回调函数就会被挂起。一旦这个 Promise 完成,co 就会恢复该回调函数:如果 Promise 被实现,yield 会返回这个实现的值,如果被拒绝,则 yield 会抛出异常。另外,co 能处理回调函数返回的结果(与 then() 相似)。

8.2 异步函数

异步函数的语法基本上可以说是实现了 co 所做的:

1
2
3
4
5
6
7
8
9
10
async function fetchJson(url) {
try {
let request = await fetch(url);
let text = await request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
}

在内部,异步函数的工作非常类似于 generators,但它并不会被翻译为 generator 函数。

8.3 变体

异步函数有以下几种变体:

函数声明: async function foo() { }
函数表达式:const foo = async function () {};
方法定义:let obj = { async foo() {} }
箭头函数:const foo = async () => {}

8.4.扩展阅读

9、扩展阅读

下面是本篇博客的重要参考资料:

其他资料:

作者:Dr. Axel Rauschmayer,时间:2015-11-15

原文链接: http://www.2ality.com/2015/11/tc39-process.html

译者:zhouweicsu