ES6 深入:未来

ES6 深入 是一个关于 ECMAScript 标准第 6 版(简称 ES6)中新增的 JavaScript 编程语言特性的系列文章。

上周关于 ES6 模块的文章 对 ES6 中的主要新功能进行了为期 4 个月的调查。

这篇文章涵盖了十几个我们之前没有深入讨论过的 功能。把它看作是一次关于这栋语言大厦中所有壁橱和形状怪异的楼上房间的有趣之旅。也许还有一两个广阔的地下洞穴。如果您还没有阅读本系列的其他部分,请看看;这一部分可能不是最好的起点!

(a picture of the Batcave, inexplicably)
“在您的左侧,您可以看到类型化数组……”

再提醒您一下:下面列出的许多功能尚未得到广泛实现。

好的。让我们开始吧。

您可能已经在使用的功能

ES6 标准化了一些之前在其他标准中存在,或者广泛实现但非标准的功能。

  • 类型化数组<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" target="_blank">ArrayBuffer</a><a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView" target="_blank">DataView</a> 这些功能都是作为 WebGL 的一部分标准化的,但此后它们已在许多其他 API 中使用,包括 Canvas、Web Audio API 和 WebRTC。无论何时需要处理大量原始二进制或数字数据,它们都非常方便。

    例如,如果 Canvas 渲染上下文缺少您想要的功能,并且您对此感觉足够强硬,您可以自己实现它

    <pre>
    var context = canvas.getContext("2d");
    var image = context.getImageData(0, 0, canvas.width, canvas.height);
    var pixels = image.data; // 一个 Uint8ClampedArray 对象
    // ... 您的代码在这里!
    // ... 在 `pixels` 中修改原始位
    // ... 然后将它们写回画布
    context.putImageData(image, 0, 0);
    </pre>

    在标准化过程中,类型化数组添加了 方法,如 .slice().map().filter()

  • Promise。 只写一段关于 Promise 的文字就像只吃一片薯片一样。不管它有多么 困难;它甚至作为一个事情来做都没有意义。该说些什么呢?Promise 是异步 JS 编程的基础。它们代表的是将在以后可用的值。因此,例如,当您调用 <a href="https://mdn.org.cn/en-US/docs/Web/API/GlobalFetch/fetch" target="_blank">fetch()</a> 时,它会立即返回一个 <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank">Promise</a> 对象,而不是阻塞。fetch 在后台继续运行,并在响应到达时会回调您。Promise 比单独的回调更好,因为它们可以很好地链接在一起,它们是一级值,具有 有趣操作,并且您可以使用更少的样板代码来获得正确的错误处理。它们可以在浏览器中进行填充。如果您还不了解 Promise,请查看 Jake Archibald 的非常深入的文章

  • 块级作用域中的函数。不应该 使用它,但您可能已经使用过它。也许是无意地。

    在 ES1-5 中,这段代码在技术上是非法的

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

    这段 if 块内的函数声明据说是禁止的。它们只允许在顶层,或在函数的最外层块中。

    但它在所有主要浏览器中都运行良好。有点。

    不兼容。每个浏览器的细节都略有不同。但它确实运行良好,许多网页仍在使用它。

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

    不幸的是,Firefox 和 Safari 尚未实现新标准。因此,现在请改用函数表达式

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

    块级函数没有在几年前标准化的唯一原因是向后兼容性约束极其复杂。没有人认为它们可以解决。ES6 通过添加一个 非常奇怪的规则 来解决这个问题,该规则仅适用于非严格代码。我无法在这里解释它。相信我,使用严格模式。

  • 函数名称。 所有主要的 JS 引擎也都长期支持对具有名称的函数的非标准 .name 属性。ES6 标准化了这一点,并通过为一些以前被认为是无名函数的函数推断出合理的 .name 使其变得更好

    <pre>
    > var lessThan = function (a, b) { return a < b; };
    > lessThan.name
    "lessThan"
    </pre>

    对于其他函数,例如作为 .then 方法参数出现的回调函数,规范仍然无法确定名称。<var>fn</var>.name 然后为空字符串。

好的功能

  • <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign" target="_blank">Object.assign(target, ...sources)</a> 一个新的标准库函数,类似于 Underscore 的 <a href="https://underscorejs.node.org.cn/#extend" target="_blank">_.extend()</a>

  • 用于函数调用的扩展运算符。 这与 Nutella 无关,尽管 Nutella 是一种美味的酱。但它是一个很棒的功能,我认为您会喜欢的。

    早在五月份,我们介绍了 剩余参数。它们是函数接收任意数量参数的一种方式,是随机、笨拙的 arguments 对象的更文明的替代方案。

    <pre>
    function log(...stuff) { // stuff 是剩余参数。
    var rendered = stuff.map(renderStuff); // 它是一个真正的数组。
    $("#log").add($(rendered));
    }
    </pre>

    我们没有说的是,存在用于向函数 传递 任意数量参数的匹配语法,这是对 fn.apply() 的更文明的替代方案

    <pre>
    // 记录数组中的所有值
    log(...myArray);
    </pre>

    当然,它适用于任何 可迭代对象,因此您可以通过编写 log(...mySet) 来记录 Set 中的所有内容。

    与剩余参数不同,在单个参数列表中多次使用扩展运算符是有意义的

    <pre>
    // Kicks 在 Trids 之前
    log("Kicks:", ...kicks, "Trids:", ...trids);
    </pre>

    扩展运算符非常适合扁平化数组数组

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

    ……但这可能是我迫切需要的功能之一。如果是这样,我怪 Haskell。

  • 用于构建数组的扩展运算符。 同样是在五月份,我们谈到了 “剩余”模式的解构。它们是获取数组中任意数量元素的一种方式

    <pre>
    > var [head, ...tail] = [1, 2, 3, 4];
    > head
    1
    > tail
    [2, 3, 4]
    </pre>

    猜猜看!存在用于将任意数量元素 放入 数组的匹配语法

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

    这遵循与用于函数调用的扩展运算符相同的规则:您可以在同一个数组中多次使用扩展运算符,等等。

  • 正确的尾调用。 这一功能太神奇了,我无法在这里解释。

    要理解这个功能,最好的起点莫过于 《计算机程序的结构和解释》第 1 页。如果您喜欢它,请继续阅读。尾调用在 第 1.2.1 节,“线性递归和迭代” 中进行了解释。ES6 标准要求实现是“尾递归”,正如该术语在其中定义的那样。

    所有主要的 JS 引擎都尚未实现它。它很难实现。但总有一天会实现的。

文本

  • Unicode 版本升级。 ES5 要求实现至少支持 Unicode 版本 3.0 中的所有字符。ES6 实现必须至少支持 Unicode 5.1.0。您现在可以在函数名称中使用 线性 B 中的字符!

    线性 A 仍然存在一些风险,因为直到版本 7.0 它才被添加到 Unicode,而且用一种从未被破译的语言编写的代码可能难以维护。

    (即使在支持 Unicode 6.1 中添加的 emoji 的 JavaScript 引擎中,您也不能使用 😺 作为变量名。出于某种原因,Unicode 联盟决定不将它归类为标识符字符。😾)

  • 长 Unicode 转义序列。 ES6 与早期版本一样,支持四位 Unicode 转义序列。它们看起来像这样:\u212A。这些很不错。你可以在字符串中使用它们。或者,如果你觉得很随意,而且你的项目根本没有代码审查策略,你可以在变量名中使用它们。但是,对于像 U+13021(𓀡)这样的字符,即埃及象形文字中一个站在头上的男人,就有一个小问题。数字 13021 有五位数字。五大于四。

    在 ES5 中,你必须写两个转义序列,一个 UTF-16 代理对。这感觉就像生活在黑暗时代:寒冷、悲惨、野蛮。ES6,就像意大利文艺复兴的黎明,带来了巨大的变化:你现在可以写 \u{13021}

  • 更好地支持 BMP 之外的字符。 .toUpperCase().toLowerCase() 方法现在可以作用于用 Deseret 字母表 写成的字符串了!

    同样,<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint" target="_blank">String.fromCodePoint(...codePoints)</a> 是一个与旧的 <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode" target="_blank">String.fromCharCode(...codeUnits)</a> 非常相似的函数,但支持 BMP 之外的代码点。

  • Unicode 正则表达式。 ES6 正则表达式支持一个新的标志,即 u 标志,它会导致正则表达式将 BMP 之外的字符视为单个字符,而不是两个单独的代码单元。例如,没有 u/./ 只匹配字符 "😭" 的一半。但 /./<strong>u</strong> 匹配整个字符。

    RegExp 上使用 u 标志还会启用更多 Unicode 敏感的区分大小写匹配和长 Unicode 转义序列。有关完整的故事,请参阅 Mathias Bynens 的非常详细的帖子

  • 粘性正则表达式。 一个与 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。

    (快速提醒:<var>number</var>.toString(base)parseInt(<var>string</var>, base) 是将数字转换为任意进制以及从任意进制转换数字的原始方法。)

  • 新的 Number 函数和常量。 这些函数非常小众。如果你感兴趣,你可以自己浏览标准,从 <a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-number.epsilon" target="_blank">Number.EPSILON</a> 开始。

    这里可能最有趣的新概念是“安全整数”范围,从 −(253 – 1) 到 +(253 – 1)(包括)。这个特殊的数字范围与 JS 一直存在。这个范围内的每个整数都可以精确地表示为 JS 数字,其最近的邻居也是如此。简而言之,它是 ++-- 按预期工作时的范围。在这个范围之外,奇数整数无法表示为 64 位浮点数,因此对可以表示的数字(都是偶数)进行增量和减量无法得到正确的结果。如果这对你的代码很重要,标准现在提供了常量 <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER" target="_blank">Number.MIN_SAFE_INTEGER</a><a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER" target="_blank">Number.MAX_SAFE_INTEGER</a>,以及一个谓词 <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger" target="_blank">Number.isSafeInteger(n)</a>

  • 新的 Math 函数。 ES6 添加了 双曲 三角 函数 它们的 反函数<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt" target="_blank">Math.cbrt(x)</a> 用于计算立方根,<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot" target="_blank">Math.hypot(x, y)</a> 用于计算直角三角形的斜边,<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2" target="_blank">Math.log2(x)</a><a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10" target="_blank">Math.log10(x)</a> 用于计算常用底的对数,<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32" target="_blank">Math.clz32(x)</a> 用于帮助计算整数对数,以及其他几个函数。

    <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign" target="_blank">Math.sign(x)</a> 获取数字的符号。

    ES6 还添加了 <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul" target="_blank">Math.imul(x, y)</a>,它执行符号乘法模 232。这是一种非常奇怪的需求……除非你正在处理 JS 没有 64 位整数或大整数的事实。在这种情况下,它非常方便。这有助于编译器。Emscripten 使用此函数在 JS 中实现 64 位整数乘法。

    类似地,<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround" target="_blank">Math.fround(x)</a> 对于需要支持 32 位浮点数的编译器来说非常方便。

结尾

这就是全部内容吗?

当然不是。我甚至没有提到 所有内置迭代器的通用原型 的对象,这个绝密的 GeneratorFunction 构造函数<a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is" target="_blank">Object.is(v1, v2)</a>Symbol.species 如何帮助支持对像 ArrayPromise 这样的内置函数进行子类化,或者 ES6 如何指定以前从未标准化过的 多个全局对象 的工作细节。

我相信我也错过了一些东西。

但是,如果你一直在关注,你对我们将要走向何方有了很好的了解。你知道 今天就可以使用 ES6 功能,如果你确实使用了它们,你就等于选择了更好的语言。

几天前,Josh Mock 告诉我,他刚刚在约 50 行代码中使用了 八种不同的 ES6 功能,甚至没有真正想过。模块、类、参数默认值、SetMap、模板字符串、箭头函数和 let。(他错过了 forof 循环。)

我的经历也是如此。这些新功能非常好地结合在一起。它们最终会影响你编写的几乎每一行 JS 代码。

与此同时,每个 JS 引擎都在争先恐后地实现和优化我们在过去几个月里一直在讨论的功能。

一旦我们完成了,这门语言就将完整。我们再也不用更改任何东西了。我将不得不寻找其他东西来做。

只是开玩笑。ES7 的提案 已经开始蓬勃发展。举几个例子

  • 指数运算符。 2 ** 8 将返回 256。在 Firefox Nightly 中实现。

  • <a href="https://github.com/tc39/Array.prototype.includes/" target="_blank">Array.prototype.includes(value)</a>. 如果此数组包含给定值,则返回 true。在 Firefox Nightly 中实现;可通过 polyfill 实现。

  • SIMD。 公开现代 CPU 提供的 128 位 SIMD 指令。这些指令一次对 2 个、4 个或 8 个相邻数组元素执行算术运算。它们可以显着加快流式音频和视频、加密、游戏、图像处理等各种算法的速度。非常底层,非常强大。在 Firefox Nightly 中实现;可通过 polyfill 实现。

  • 异步函数。 我们在有关生成器的 帖子 中提到了此功能。异步函数就像生成器,但专门用于异步编程。当你调用生成器时,它返回一个迭代器。当你调用异步函数时,它返回一个 Promise。生成器使用 yield 关键字暂停并生成一个值;异步函数使用 await 关键字暂停并等待一个 Promise。

    很难用几句话来描述它们,但异步函数将成为 ES7 的里程碑式功能。

  • 类型化对象。 这是类型化数组的后续版本。类型化数组的元素是类型化的。类型化对象只是一个属性是类型化的对象。

    <pre>
    // 创建一个新的结构类型。每个 Point 都具有两个字段
    // 命名为 x 和 y。
    var Point = new TypedObject.StructType({
    x: TypedObject.int32,
    y: TypedObject.int32
    });

    // 现在创建一个该类型的实例。
    var p = new Point({x: 800, y: 600});
    console.log(p.x); // 800
    </pre>

    你只会在性能方面这样做。与类型化数组一样,类型化对象提供了一些类型的优点(紧凑的内存使用和速度),但是在每个对象的基础上,按需使用,与所有内容都是静态类型化的语言形成对比。

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

    在 Firefox Nightly 中实现。

  • 类和属性装饰器。 装饰器是添加到属性、类或方法上的标签。一个示例展示了这一点

    <pre>
    import debug from "jsdebug";

    class Person {
    @debug.logWhenCalled
    hasRoundHead(assert) {
    return this.head instanceof Spheroid;
    }
    ...
    }
    </pre>

    @debug.logWhenCalled 是这里的装饰器。你可以想象它对方法做了什么。

    提案详细解释了它是如何工作的,并提供了许多示例。

我还有另一个激动人心的发展需要提及。这不是语言功能。

TC39,ECMAScript 标准委员会,正在朝着更频繁的发布和更公开的流程迈进。ES5 和 ES6 之间相隔了六年。委员会的目标是在 ES6 发布后仅 12 个月内发布 ES7。标准的后续版本将以 12 个月的节奏发布。上面列出的一些功能将及时准备就绪。它们将“赶上列车”,成为 ES7 的一部分。那些在那个时间范围内没有完成的功能可以赶上下一班列车。

分享 ES6 中如此之多的好东西真是太有趣了。能说这样规模的功能倾销可能永远不会再发生也是一种乐趣。

感谢您加入我们一起深入 ES6!希望您喜欢它。保持联系。

关于 Jason Orendorff

更多 Jason Orendorff 的文章…


6 条评论

  1. Johannes Brodwall

    感谢您撰写了一系列很棒的文章。ES6 是开发者激动人心的时代。

    2015 年 8 月 22 日 07:12

  2. Igor

    谢谢!这是一个很棒的系列,可以从中发现和学习。对即将到来的所有新功能感到兴奋!

    2015 年 8 月 22 日 16:45

  3. Luke

    function.name 很有趣——我想知道它如何使用,以及它如何与缩小器配合使用?

    它通常只是用于调试工作吗?

    2015 年 8 月 23 日 21:46

  4. voracity

    感谢您撰写的所有文章,它们棒极了——写得非常好,很有见地。如果您(以及其他人)决定再写一些***深入的内容,我不会抱怨。

    我不知道 ES7 有可能使用装饰器。这让我很高兴。在(过程定义的)模块内部以及函数/变量中也是一个好主意。(当然,后者没有那么有用,但它可以使某些事情更简洁——例如动态注释/文档系统。)

    “异步函数将是 ES7 的里程碑式功能。”:) 我非常兴奋。这可能是 JavaScript 历史上最大的变化(在实践中)。

    2015 年 8 月 23 日 23:45

  5. Phil Dokas

    感谢您撰写这个系列,它对许多好东西来说是一个非常有价值且易于理解的课程。我希望您能为 ES7 及以后的版本再做一次!

    2015 年 8 月 29 日 16:39

  6. Rahul garg

    谢谢。我从您的系列文章中学习了很多关于 ES6 功能的知识,仍在学习,还有很长的路要走。它们将非常有用。

    2015 年 9 月 7 日 10:58

这篇文章的评论已关闭。