- 原文地址:Interesting ECMAScript 2017 proposals that weren’t adopted
- 原文做者:Kaelan Cooter
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:Colafornia
- 校对者:jasonxia23 kezhenxu94
要跟上全部新功能提案的进度并不容易。每一年,管理 JavaScript 发展的委员会 TC39 都会收到数十个提案。因为大多数提案都不会到达第二阶段,所以很难肯定哪些提案值得关注,哪些提案只是奇思妙想(或者称之为异想天开)。javascript
因为如今愈来愈多的提案涌现出来,想要只停留在这些特性提案的顶层会很是困难。在过去介于 ES5 和 ES6 之间的六年时间里 JavaScript 的发展脚步很是保守。自 ECMAScript 2016 (ES7) 发布,发布过程要求为每一年发布一次,而且更加标准化。html
随着近些年来 polyfills 和转译器的流行,一些尚属早期(early-stage)的提案甚至在还未最终肯定前就已经被普遍使用了。而且,因为提案在被采纳以前会有很大变更,一些开发者可能会发现他们所使用的特性永远不会变成 JavaScript 的语言实现。前端
在深刻研究那些我以为很好玩的提案以前,咱们先花点时间熟悉一下目前的提案流程。java
Stage 0 “稻草人” —— 这是全部提案的起点。在进入下一阶段以前,提案的内容可能会发生重大变化。目前尚未提案的接收标准,任何人均可觉得这一阶段提交新的提案。无需任何代码实现,规范也无需合乎标准。这个阶段的目的是开始针对该功能特性的讨论。目前已经有超过 20 个处于 stage 0 的提案。react
Stage 1 “提案” —— 一个真正的正式提案。此阶段的提案须要一个“拥护者”(即 TC39 委员会的成员)。此阶段需仔细考虑 API 并描述出任何潜在的、代码实现方面的挑战。此阶段也需开发 polyfill 并产出 demo。在这一阶段以后提案可能会发生重大变化,所以需当心使用。目前仍处于这一阶段的提案包括了已望穿秋水的 Observables type 与 Promise.try 功能。android
Stage 2 “草案” —— 此阶段将使用正式的 TC39 规范语言来精确描述语法。在此阶段后仍由可能发生一些小修改,可是规范应该足够完整,无需进行重大修订。若是一个提案走到了这一步,那么颇有可能委员会是但愿最终能够实现该功能的。ios
Stage 3 “候选” —— 该提案已获批准,仅当执行做者提出要求时才会作进一步的修改。此时你能够期待 JavaScript 引擎中开始实现提案的功能了。在这一阶段草案的 polyfill 能够安全无忧使用。git
Stage 4 “完成” —— 说明提案已被采纳,提案规范将与 JavaScript 规范合并。预计不会再发生变化。JavaScript 引擎将发布它们的实现。截至 2017 年 10 月,已经有 9 个已完成的提案,其中最引人关注的是 async functions。github
因为提案愈来愈多,思考一番,如下几个提案是其中更有趣的。web
ECMAScript 2015 中引入了迭代器 iterator,其中包含了 for-of 循环语法。这使得循环遍历可迭代对象变得至关容易,而且能够实现你本身的可迭代数据结构。
遗憾的是,遍历器没法用于表示异步的数据结构如访问文件系统。虽然你能够运行 Promise.all 来遍历一系列的 promise,但这须要同步肯定“已完成”的状态。
例如,可使用异步迭代器来遍历异步内容,按需读取文件中内容,而不是提早读取文件中的全部内容。
你能够经过简单地同时使用 generator 生成器语法和 async-await 语法来定义异步生成器函数:
async function* readLines(path) {
let file = await fileOpen(path);
try {
while (!file.EOF) {
yield await file.readLine();
}
} finally {
await file.close();
}
}
复制代码
异步生成器函数示例。
能够在 for-await-of 循环中使用这个异步生成器:
for await (const line of readLines(filePath)) {
console.log(line);
}
复制代码
使用 for-await-of。
任意具备 Symbol.asyncIterator 属性的对象都被定义为 async iterable,而且可以使用于新的 for-await-of 语法中。这有一个具体可运行的示例:
class LineReader() {
constructor(filepath) {
this.filepath = filepath;
this.file = fileOpen(filepath);
}
[Symbol.asyncIterator]: {
next() {
return new Promise((resolve, reject) => {
if (this.file.EOF) {
resolve({ value: null, done: true });
} else {
this.file.readLine()
.then(value => resolve({ value, done: false }))
.catch(error => reject(error));
}
});
}
}
}
复制代码
使用 Symbol.asyncIterator 的示例。
这一提案 目前处于 stage 3,浏览器已经开始实现了。处于这一阶段意味着它颇有可能会被合并入标准并能够在主流浏览器中使用。可是,在此以前,规范可能会有一些小修改,所以如今使用异步迭代器会带来必定程度的风险。
regenerator 项目目前已为异步迭代器提案提供了基本支持。可是,它自己并不支持 for-await-of 循环语法。Babel 插件 transform-async-generator-functions 既支持异步生成器又支持 for-await-of 循环语法。
这个提案 建议向 [ECMAScript 2015 中引进的 class 语法] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) 中加入公共字段、私有字段与私有方法。该提案是通过多个竞争提案漫长讨论和竞争后的结果。
使用私有字段和方法与对应的公共字段和方法相似,可是私有字段名方法名前会有一个 # 号。任何被标记为私有的方法和字段都不会在类以外可见,从而确保内部类成员的强封装。
下面是一个类 React 组件的假设示例,组件在私有方法中使用了公共和私有字段:
class Counter {
// 公共字段
text = ‘Counter’;
// 私有字段
#state = {
count: 0,
};
// 私有方法
#handleClick() {
this.#state.count++;
}
// 公共方法
render() {
return (
<button onClick={this.handleClick.bind(this)}>
{this.text}: {this.#state.count.toString()}
</button>
);
}
}
复制代码
使用了私有字段与私有方法的类 React 组件。
Babel 目前尚未提供私有类字段与方法的 polyfill,可是不久就会实现。公共字段已有 Babel 的transform-class-properties 插件支持,但它依赖于一个已被合并入统一公共/私有字段提案的老提案。此提案于 2017 年 9 月 1 日 进入 stage 3,所以使用任何可用的 polyfill 都是安全的。
提案在被引入后也可能发生翻天覆地的变化,装饰器就是一个很好的例子。Babel 的第五代版本实现了本来 stage 2 阶段装饰器的规范,其将装饰器定义为接收 target,name 与属性描述的函数。如今最流行的转译装饰器方式是经过 Babel 的transform-legacy-decorators 插件,其实现的是旧版的规范。
新的提案 大不相同。再也不做为具备三个属性的函数,如今咱们对改变描述符的类成员 —— 装饰器进行了正式描述。新的“成员描述符”与ES5中引入的属性描述符接口很是类似。
如今有两种具备不一样 API 的不一样类型的装饰器:成员装饰器与类装饰器。
在规范中,“额外”是指可由装饰器添加的成员描述符的可选数组。这将容许装饰器动态建立新的属性与方法。好比,你可让装饰器给属性建立 getter 与 setter 函数。
与旧规范相似,新规范容许修改类成员的描述符。此外,仍然容许在对象字面量的属性上使用装饰器。
在最终肯定以前,规范极可能会发生重大变化。语法中有一些模棱两可之处,旧规范的许多痛点尚未获得解决。装饰器是语言的一个大型语法扩展,所以能够预料到这种延迟。遗憾的是,若是新的提案被采纳,你将不得不彻底重构你的装饰器函数,以适用于新的接口。
许多库做者选择继续支持旧的提案和 Babel 的 legacy-decorators 插件,即便新的提案已经处于 stage 2,旧的仍然处于 stage 0。core-decorators 做为最受欢迎的使用装饰器的 JavaScript 开源库,就采用了这种方法。将来几年中,库的做者们颇有可能会继续支持旧的提案。
也有可能这一新提案会被撤回,取而代之的是另外一新提案,装饰器提案有可能不会在 2018 年并入 JavaScript。你能够在 Babel 完成新的转译插件 后使用新的装饰器提案。
ECMAScript 第六版中添加了 import 语句,并最终肯定了新的模块系统的语义。就在近期,主流浏览器发布更新以提供对其的支持,尽管它们对于规范的实现略有不一样。NodeJS 在 8.5.0 版本中对于仍带有实验标志的 ECMAScript 的模块规范提供了初步支持。
可是,该提案缺乏一种异步导入模块的方法,这使得难以在运行时动态导入模块。如今,在浏览器中动态加载模块的惟一方法,就是动态插入类型为 “module” 的 script 标签,将 import 声明做为其文本内容。
实现异步导入模块的一种内置方法是提案 动态 import 语法,它会调用一个“类函数”的导入模块加载表单。这种动态导入语法能够在模块代码与普通脚本代码中使用,从而为模块代码提供了一个方便的切入点。
去年有一个提案提出 System.import() 函数来解决这个问题,可是该提案没有被采纳进入最终的规范。新提案目前处于 stage 3,有望在年末前被列入规范中。
提议的可观察类型 Observable type 提供了一种处理异步数据流的标准化方法。它们已经以某种形式在许多流行的 JavaScript 框架如 RxJS 中实现。目前的提案很大程度上借鉴了这些框架的思路。
Observable 对象由 Observable 构造器建立,接收订阅函数做为参数:
function listen(element, eventName) {
return new Observable(observer => {
// 建立一个事件处理函数,能够将数据输出
let handler = event => observer.next(event);
// 绑定事件处理函数
element.addEventListener(eventName, handler, true);
// 返回一个函数,调用它即去掉订阅
return () => {
// 解除元素的事件监听
element.removeEventListener(eventName, handler, true);
};
});
}
复制代码
Observable 构造器的使用。
使用订阅函数去订阅一个 observable 对象:
const subscription = listen(inputElement, “keydown”).subscribe({
next(val) { console.log("Got key: " + val) },
error(err) { console.log("Got an error: " + err) },
complete() { console.log("Stream complete!") },
});
复制代码
使用 observable 对象。
subscribe() 函数返回了一个订阅对象。这个对象具备取消订阅的方法。
Observable 不该混淆于 已废弃的 Object.observe 函数,Object.observe 是能够观察对象变化的一种方法。其已被 ECMAScript 2015 中更通用的的实现 Proxy 所替代。
Observable 目前处于 stage 1,但它已被 TC39 委员会标记为 “ready to advance” 并得到了浏览器厂商的大力支持,所以有望很快推动到下一阶段。如今你就已经能够开始使用这一提案的特性了,有三种 polyfill 实现 可供选择。
CoffeeScript 曾因以一切皆为表达式 而名声大噪,尽管它的流行程度已经衰减,可是它对 JavaScript 近期的发展产生了重大影响。
do 表达式提出了一种将多个语句包装在一个表达式中的新语法。能够以以下方式编写代码:
let activeToDos = do {
let result;
try {
result = fetch('/todos');
} catch (error) {
result = []
}
result.filter(item => item.active);
}
复制代码
do 表达式示例。
do 表达式的最后一个语句将做为完成值,被隐式地返回。
do 表达式在 JSX 中很是有用。与复杂的三元表达式不一样,do 表达式可使得 JSX 中的流程控制更可读。
const FooComponent = ({ kind }) => (
<div> {do { if (kind === 'bar') { <BarComponent /> } else if (kind === 'baz') { <BazComponent /> } else { <OtherComponent /> } }} </div>
)
复制代码
JSX 的将来?
Babel 已有插件 可转译 do 表达式。此提案目前处于 stage 1,关于如何与 generator 和 async 函数一块儿使用,还存在一些重要的开放问题,所以规范可能会发生重大变化。
受 CoffeeScript 启发而来的又一个提案是 optional chaining,它带来了一种访问对象属性的简单方法,面对值有可能为 undefined 和 null 的对象属性无需使用冗长的三元运算符了。它与 CoffeeScript 的存在操做符相似,可是缺乏一些值得注意的特性,好比范围检查和可选赋值。
示例:
a?.b // 若是 `a` 是 null/undefined 则返回 undefined,不然则返回 `a.b` 的值
a == null ? undefined : a.b // 使用三元表达式
复制代码
提案目前处于 stage 1,已有名为 babel-plugin-transform-optional-chaining 的 Babel 插件实现。TC39 委员会在2017 年 10 月的最后一次会议 中对它的语法表示担心,可是其仍有可能被采纳。
编写能在每一个环境中运行的 JavaScript 代码并不容易。在浏览器中,全局对象是 window —— 除非处于 web worker 中,此时全局对象是它自己。在 NodeJS 中则为 global,可是这是在 V8 引擎之上添加的东西,并非规范的一部分,因此直接在 V8 引擎中运行代码时,global 对象不可用。
待标准化提案 提出了能够在全部引擎和运行环境中使用的全局对象。提案目前处于 stage 3,所以不久便会被采纳。
TC39 委员会正在审议五十多项活跃提案,其中还未包括二十多个处于 stage 0 还没有推动的提案。
你能够在 TC39 的 GitHub 页面 查看活跃提案列表。能够在 stage 0 提案模块找到一些更粗略的想法,包括了像方法参数装饰器和新的模式匹配语法。
也能够在会议记录 和议程 仓库了解委员会的优先事项和目前正在处理的问题。演讲资料也陈列在会议记录中,若是你对演讲有兴趣,也能够进行查阅。
在最近的几回 ECMAScript 修订中有几个重要的语法修改提案,这彷佛也是一种趋势。ECMAScript 正在加快变革脚步,每一年都有愈来愈多的提案,2018 版本彷佛会比 2017 版本采纳更多的提案。
今年,在语言中添加改善“生活质量”的提案规模较小,如内置对象的类型检查和给 Math 模块添加度数与弧度助手提案。这类提案将添加到标准库中,而非修改语法。它们容易进行 polyfill,有助于减小第三方库的使用。因为无需改变语法,因此很快就可使用,在提案阶段花费的时间也较少。
新的语法当然优秀,但我更但愿将来能够见到更多这种类型的提案。JavaScript 常常被人诟病缺乏优秀的标准库,但很明显你们正在努力去改变。
LogRocket 是一个前端日志记录工具,它可让你像在本身的浏览器中同样重播问题。无需再猜想错误发生的缘由或是向用户索要截图和日志转存,LogRocket 容许重播会话来快速定位错误源头。不管使用什么框架,LogRocket 能够在任意应用中使用,并拥有从 Redux,Vuex 和 @ngrx/store 中记录上下文的插件。
除了能够将 Redux 的 action 和 state 记入日志,LogRocket 还能够记录控制台日志,JavaScript 报错,调用栈信息,网络请求、响应头和实体信息,浏览器的元信息和自定义日志。它还利用 DOM 在记录页面上的 HTML 和 CSS,即便是最复杂的单页面应用程序,也能够从新绘制像素级完美的视频。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。