返回首页 深度解析 ECMAScript 6

未来前景

这篇文章涵盖了十多个我们从未详细讨论过的新函数。我们可以把这当作在语言的豪宅中进行了一次有趣的游览,游览中我们参观了所有的壁橱和楼上奇形怪状的房间。也许有一两个超级大的地下洞穴。如果你还没有看过该系列的其他部分,请看这里;本部分未必是最好的开始的地方!

alt text"在你的左边,你可以看到类型数组……"

一个提前警告:许多下述功能还没有得到广泛的实施。好,让我们开始吧。

你已经使用的函数

ES6 标准化了一些先前使用其他标准或被广泛使用但并无标准的函数。

  • 类型数组缓冲数组数据视图。这些都曾被标准化为 WebGL 的一部分,从那以后他们已经被应用于许多应用程序编程接口,包括画布、网络音频应用程序编程接口和网页实时通信。当你需要处理大量的原始二进制或数字数据时,使用它们会非常方便。

例如,如果画布渲染上下文对象而失去了你想要的特征并且你觉得自己充分掌握了它的核心,你便可以自己将它实现。

var context = canvas.getContext("2d");
var image = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = image.data;  // a Uint8ClampedArray object
// ... Your code here!
// ... Hack on the raw bits in `pixels`
// ... and then write them back to the canvas:
context.putImageData(image, 0, 0);

在标准化过程中,类型化数组重新取用了.slice().map().filter()方法

  • Promises。只写一个有关 promises 的段落就像一口只吃一片薯片。无论这有多难,它都是一件几乎无意义去做的事情。怎么说呢?Promises 就像异步 JS 编程的基石,他们代表了将被使用的值。举个例子,当你调用fetch()函数而不是阻塞模式时,它会立即返回一个Promise 对象。Fetch 将进入后台工作,当反射代码出现时它会叫你回来。Promises 因链接工作的很好而比单独使用 callbacks 更胜一筹,他们是拥有着趣味操作的一类值,你可以样板文件更少的情况下拥有错误处理权。他们增强了浏览器的性能。如果你还不知道关于 promises 的所有,先去看看杰克·阿奇博尔德的深入探究文章

  • 块级作用域函数。你本不应该使用它,但在无意中你也许已经使用过了。 在 ES1-5 中,这段代码在技术上是非法的:

该函数在 if 块中声明被认为是非法的。

if (temperature > 100) {
  function chill() {
    return fan.switchOn().then(obtainLemonade);
  }
  chill();
}

他们只在顶层或函数的最外层块内合法。

但是可以说,它被用于所有主要浏览器。

不兼容。每个浏览器都有一点点不同,但它依然可以工作并且许多网页仍然在使用它。

谢天谢地,ES6 标准化了这一点。函数被提升到了封闭块的顶部。

不幸的是,Firefox 和 Safari 没有执行这项标准。所以就目前而言,使用函数表达式来代替。

if (temperature > 100) {
  var chill = function () {    
    return fan.switchOn().then(obtainLemonade);
  };
  chill();
}

块作用域函数没有在多年前就被标准化的唯一原因是向后兼容的限制极其复杂。没有人认为它能被解决。ES6 通过添加了一项非常奇怪的规则完成了这项艰巨的任务,这个规则就是仅适用于非严格代码。我无法解释它。相信我,使用严格的模式。

  • 函数名。所有主要 JS 引擎还长期支持有名函数的一个非标准.name属性。ES6 把这标准化,通过为一些在此之前被认为无名的函数推断合理的.name,来将其变得更好。
> var lessThan = function (a, b) { return a < b; };
> lessThan.name
    "lessThan"

对于其他函数,如回调体现为.then的参数,该规范仍然没有制定一个名字。fn.name 便成了空字符串。

好的事情

早在五月,我们就推出了其余的参数。他们是函数的一个接收任意数量参数的方式,是随机、复杂难懂的参数对象的更文明的选择。

function log(...stuff) {  // stuff is the rest parameter.
  var rendered = stuff.map(renderStuff); // It's a real array.
  $("#log").add($(rendered));
}

我们没有说的是,有传递给函数任意数量参数的匹配语法,能够更文明的替代 fn.apply()

// log all the values from an array
log(...myArray);

当然,它与任何可迭代对象一起工作,这样你就可以将所有东西记录在一个集里并记录为 log(... mySet)

与其他参数不同的是,在单一的参数列表内多次重复使用展开运算符是有效的。

// kicks are before trids
log("Kicks:", ...kicks, "Trids:", ...trids);

展开运算符可以方便地扁平化数组中的一个数列。

> var smallArrays = [[], ["one"], ["two", "twos"]];
> var oneBigArray = [].concat(...smallArrays);
> oneBigArray
    ["one", "two", "twos"]

...但也许这是一个只有我渴求的迫切需要。如果是这样,我责怪哈斯克尔。

  • 构造数组的展开运算符。同样追溯回五月,我们谈到了解构的“休息”模式。它们是从一个数列中获取任意数量元素的一种方法。
> var [head, ...tail] = [1, 2, 3, 4];
> head
    1
> tail
    [2, 3, 4]

猜猜发生了什么!竟然有把任意数量元素添加到一个数列中的匹配语法。

> var reunited = [head, ...tail];
> reunited
    [1, 2, 3, 4]

所有函数调用的展开运算符的使用规则都相同:你可以在同一数列中多次使用展开运算符等等。

  • 合适的尾调用。这对于我来说太不可思议了,我试着在这里解释。

要想理解这个特征,最好从《计算程序的结构与解读》第一页开始了解。如果你喜欢它,就继续阅读下去。尾调用在1.2.1章节“线性递归与迭代”中得到了阐述。ES6 标准要求实现“尾递归”,这个名词在其中就有定义。

到现在为止还没有主要 JS 引擎对其进行实施,因为这比较难以实现。但是一切都很好。

正文

  • Unicode 版本升级。ES5 至少需要实现支持 Unicode 版本 3.0 的所有字符,ES6 至少需要实现支持 Unicode 版本 5.1.0 的所有字符。现在你可以使用Linear B中的字符来作为你的函数名了!

    Linear A还是有一点冒险。一是因为直到 Unicode 版本 7.0 它才加入,二是因为很难保持用从未被破解的语言编写代码。

(即使在 JavaScript 引擎中支持在 Unicode 6.1 中加入表情符号,你也不能使用 😺 作为变量名。出于某种原因,Unicode 协会决定不将其作为标识符的分类。😾)

  • 长 Unicode 转义序列。如早期版本,ES6 支持四位 Unicode 转义序列。他们是类似于这样的:\u212A。这样很好。你可以在字符串中使用它们。如果你感觉很有趣而且你的项目没有代码审查政策,你就可以使用它们作为变量名。不过对于像 U+13021 ( ) 这样的字符,它是一个男孩在倒立的埃及象形文字,有一个小问题。数字 13021 有五位数字,超过了四位。

在 ES5 中,你必须编写两个 escape,一个 UTF-16代理对。这完全感觉像是生活在黑暗年代:寒冷,苦不堪言,野蛮。ES 就像意大利文艺复兴时期的曙光,带来了巨大的变化:你现在可以编写带有\U{13021}的字符串。

  • 为 BMP 以外的字符提供更好的支持。.现在,toUpperCase().toLowerCase()方法用于Desert 字母表的字符串编写!

本着相同的精神,String.fromCodePoint(...codePoints)字符串与之前的String.fromCharCode(...codeUnits)字符串非常相似,但是它支持BMP以外的代码点。

  • Unicode 的正则表达式。ES6 正则表达式支持一个新的标志—— u 标志,它会导致正则表达式把 BMP 外的字符当作单个字符对待,而不是作为两个单独的代码单元。例如,没有u,/./只会匹配一半的字符“😭”。但是/./u会匹配到整个字符。

把u标志放在正则表达式上还实现了更多支持 Unicode 的不区分大小写的匹配和长 Unicode 转义序列。对于整个故事,请见马迪亚斯·宾恩斯的详细发布

  • 粘性正则表达式。非 Unicode 相关特征是 y 标志,也称为粘性标记。粘性正则表达式只查找由它的.lastIndex属性给出的、由确切开端开始的匹配。如果没有匹配存在,粘性正则表达式会立即返回 null,而不是扫描前面的字符串在其他地方寻找一个匹配。

  • 官方国际化规格。ES6 实现了提供的任何国际化功能必须支持ECMA-402,它是 ECMAScript 的 2015 年国际化 API 规范。这个单独的标准指定了Intl对象。Firefox,Chrome 和 IE11+ 已经完全支持它了。Node 0.12 也是。

  • 二进制与八进制数字。如果你需要一个奇特的方式来编写数 8,675,309,并且 0x845fed 完成不了这项任务,你现在可以编写 0o41057755(八进制)或 0b100001000101111111101101 (二进制)。

Number(str)现在也承认这种格式的字符串:Number(“0b101010”)返回 42。

(快速提示:number.toString(base)parseInt(string, base) 都是从任意基址做数字转换的原始方式。)

  • 新的数字函数和常量。这些都非常小众。如果你感兴趣,可以从Number.EPSILON开始自己浏览标准。

也许这里最有趣的新理念是“安全整数”的作用域,包括从-(253-1)到+(253-1)。这种数字的特殊作用域和 JS 的存在时间一样久。在此作用域内每个整数都可以被确切表示为一个 JS 数,就是与之最近的那个数。总的来说,这是++和—的正常工作作用域。在此作用域之外,奇数不能被表示为 64 位浮点数,所以递增和递减可表示的数(都是偶数)不能得出正确结果。万一这关系到你的代码,这项标准现在提供了Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER常数,还有谓语Number.isSafeInteger(n)

  • 新的数学函数。ES6 加入了双曲三角函数和他们的反函数,Math.cbrt(x)用来计算立方根, Math.hypot(x, y)用来计算直角三角形的斜边, Math.log2(x)Math.log10(x)用来计算常见的对数,Math.clz32(x)用来帮助计算整数对数,等等。

Math.sign(x)获取数的符号。

ES6 还增加了Math.imul(x, y),它标志着乘法模 232。这是一件很奇怪的事情……除非你在 JS 没有 64 位整数或大整数的事实环境中工作。在这种情况下,这是非常方便的。这帮助了程序编译。在 JS 中 Emscripten 使用此函数来实现 64 位整数的乘法。

同样,Math.fround(x)对于需要支持 32 位浮点数的编译程序来说也是很有帮助的。

结束

这是一切吗?

并不是。我甚至没有提及所有内置迭代器的共同原型对象,绝密的 GenerationFunction 构造函数Object.is(v1, v2),Symbol.species 是怎样帮助支持像 Promise 和 Array 的子类内置函数,或 ES6 是如何指定那些从未被标准化过的多项全局工作的细节的。

我确定我还忘记了一些东西。

但是如果你一直关注着,你对于我们将要走向的方向有一幅美丽的蓝图,你便知道从今天起你可以使用 ES6 函数了。如果你这样做了,你便是选择了一种更好的语言。

几天前乔希·默克对我说,他刚刚在约50行代码中用了八种不同的 ES6 函数,然而用的时候并没有真的去考虑它。他用了模块、类、参数默认值、集合、映射、模板字符串、箭头函数以及让。(他忘记了 for-of 循环。)

这同样也是我的经历。这些新函数结合在一起十分好用。最终你会发现,它们会出现在你编写的几乎每一行 JS 代码中。

同时,每个 JS 引擎都在加紧实施和优化那些我们前几个月一直在讨论的函数。

一旦我们完成了,语言将会是完整的。我们再也不用改变任何事情了。我得去找别的工作了。

只是开个玩笑。ES7 的提案已经初见端倪。

  • 求幂运算符。2**8 将返回 256。这将在火狐夜间浏览器中实现。

  • Array.prototype.includes(值)。如果此数组包含给定的值便返回 true。在火狐夜间浏览器和 Polyfillable 中得到实现。

  • SIMD。公开现代 CPU 提供的 128 位SIMD指令。这些指令在 2、4 或 8 相邻的数组元素中依次执行算术运算。它们可以大大加快各种各样用于音频和视频流、加密技术、游戏、图像处理等等的算法。简约而不简单。在火狐夜间浏览器和 polyfillable 中实现。

  • 异步函数。我们在生成器的作用上暗示了这个函数。异步函数就像生成器,但它专门用于异步编程。当你调用一个生成器时,会返回一个迭代器。当你调用一个异步函数时,会返回一个 promise。生成器利用关键词“theyield”来实现暂停并产生一个值;异步函数使用关键词“await”来实现暂停并等待一个 promise。

很难用几句话来描述他们,但是异步函数会是 ES7 的一个标志性特征。

  • 类型对象。这是一个后续类型数组。类型化数组有类型化的元素。类型化对象只是拥有类型化属性的对象
// Create a new struct type. Every Point has two fields
// named x and y.
var Point = new TypedObject.StructType({
  x: TypedObject.int32,
  y: TypedObject.int32
});
// Now create an instance of that type.
var p = new Point({x: 800, y: 600});
console.log(p.x); // 800

你只会出于性能的原因而这样做。像类型数组、类型对象提供了一些类型化的好处(紧凑内存使用和速度),但在逐对象和选择性加入的基础上,相比之下语言都是静态类型。

它们作为 JS 的编译目标也很有趣。

实现在火狐夜间版。

  • 类和属性装饰。在属性、类或方法上添加装饰标签。一个例子展示了这是什么:
import debug from "jsdebug";
class Person {
  @debug.logWhenCalled
  hasRoundHead(assert) {
    return this.head instanceof Spheroid;
  }
  ...
}

@debug.logWhenCalled 在这里是装饰。你可以想象它在这个方法中起到了什么作用。

该提案通过举例说明了它工作的详细细节。

还有一个令人兴奋的发展,我不得不提。这不是一个语言特征。

TC39, the ECMAScript 标准委员会,正在走向更频繁的发布和更公开的进程。ES5 和 ES6 之间隔了六年。该委员会旨在 ES6 之后短短的 12 个月内发布 ES7。随后该标准将形成每 12 个月颁布一个版本的节奏。上述的一些特性正在准备中。它们将“赶上这班火车”成为 ES7 的一部分。那些在该时间内没有完成的将会乘坐下一班火车。

十分开心能分享 ES6 的许许多多的令人惊叹的好处。同时也可以非常高兴地在这里说,如此规模的特征转储可能永远不会再发生了。

感谢大家参加“深度解析 ES6”!希望大家会喜欢。保持联系。

上一篇: 模块