zhongshan

ECMAScript 2016的那些你不知道的事儿 - NCZOnline

原文链接: www.nczonline.net

相比 ECMAScript 6 (也被称为 ECMAScript 2015), ECMAScript 2016在 JavaScript 的语言规范上有小幅度的更新。由于ECMAScript版本会每年一更,每个新版其实就是所有就绪特性的集合。有鉴于此,大多数文章仅列出 ECMAScript 2016 中两个显著的变化。

  1. 增加了乘幂运算符(**)。
  2. 增加了Array.prototype.includes()方法。

这两项功能对JavaScript开发者有着最直接的影响,实际上, ECMAScript 2016 还有一些经常被人忽略的重要更新。在我写的书Understanding ECMAScript 6中有提到这些更新,但是我仍然会收到关于它的一些疑问,所以在这篇文章中,我想深入地探讨一下 ECMAScript 2016 。

首先,我会陈述 ECMAScript 2016 的一些更新,然后再讲讲其背后的理论基础。

更新点

ECMAScript 2016 规定,"use strict"严格模式不允许用于参数具有默认值、使用解构参数或多余参数的函数内部,规范将简单参数定义为仅包含标识符的参数列表(ECMAScript 5 仅支持简单参数列表)[1]. 这一更新会影响所有的函数类型,包括函数声明、函数表达式、箭头函数和对象字面量。以下是一些示例代码。

// this is okay
function doSomething(a, b) {
    "use strict";

    // code
}

// syntax error in ECMAScript 2016
function doSomething(a, b=a) {
    "use strict";

    // code
}

// syntax error in ECMAScript 2016
const doSomething = function({a, b}) {
    "use strict";

    // code
};

// syntax error in ECMAScript 2016
const doSomething = (...a) => {
    "use strict";

    // code
};

const obj = {

    // syntax error in ECMAScript 2016
    doSomething({a, b}) {
        "use strict";

        // code
    }
};

即使函数是一个具有非简单参数的函数,你仍然可以在函数体之外全局使用"use strict",以使该函数在严格模式下执行。例如:

// this is okay
"use strict";

function doSomething(a, b=a) {
    // code
}

在这种情况下,函数体外的"use strict"语法是正确的。即使你使用 ECMAScript 模块,也不必担心这个问题,因为它会以严格模式执行所有代码。

为什么要做这项更新?

鉴于严格模式和非简单参数列表的工作方式,此项更新显得举足轻重。当在 ECMAScript 5 中使用严格模式时,解构和默认参数值不再存在,因此先解析参数列表然后再使用"use strict"就不会有问题。此时,"use strict"不能影响解析参数列表的结果,它只用于验证参数标识符(不允许重复并检查被禁止的标识符,例如evalarguments)。然而,随着在 ECMAScript 6 中引入解构和默认函数参数值,情况已经有所改变,因为规范指出参数列表应该按照与函数体相同的模式进行解析(这意味着"use strict"在函数体内必须触发严格模式)。

首先要意识到的是严格模式需要改变JavaScript代码的解析和执行[2]。举个非常简单的例子,严格模式不允许旧式的八进制数值表示形式(如070)。如果代码在严格模式下解析,070会抛出语法错误。考虑到这一点,你认为以下代码应该怎样写?

// syntax error in ECMAScript 2016
function doSomething(value=070) {
    "use strict";

    return value;
}

如果你用JavaScript解析器解析这段代码,参数列表则会在函数体之前被解析。这意味着,070会被解析为合法的值,之后在函数体中遇到"use strict",它会告诉解析器,“实际上你应该在严格模式下解析参数列表”。于是,解析器将不得不再次在严格模式下回溯并重新解析参数列表,因此会抛出070的语法错误。这可能还无足轻重,但如果默认参数值更复杂呢?

// syntax error in ECMAScript 2016
function doSomething(value=(function() {
   return doSomeCalculation() + 070;
}())) {
    "use strict";

    return value;
}

在其默认参数值是一个函数的情况下,将会引发更多的问题。你要理清更多的tokens,还必须将函数设置为默认在严格模式下运行。要确保默认参数表达式被正确解析,并在严格模式下运行,是极度复杂的。解构参数包含默认值,同样会导致类似的问题。例子如下:

// syntax error in ECMAScript 2016
function doSomething({value=070}) {
    "use strict";

    return value;
}

这里,解构参数value具有严格模式下不允许的默认值,会导致与默认参数值相同的问题。

最后,似乎TC-39决定[3]简单地禁止函数体使用"use strict",以避免上述的错误发生,这在 ECMAScript 5 中是没有说明的。这意味着具有默认参数值、解构参数或多余参数的函数在函数体中不能有"use strict"。这也包括"use strict"没有作用的情况,例如:

function outer() {
    "use strict";

    // syntax error in ECMAScript 2016
    function doSomething(value=070) {
        "use strict";

        return value;
    }
}

在上面的代码中,将具有非简单参数的函数嵌套在具有"use strict"的另一个函数中。doSomething()自动处于严格模式下,但JavaScript引擎仍会在doSomething()的函数体中的"use strict"指令上抛出语法错误。

替代方案

这种更新一般不会影响很多开发者,这也可能是你不知道它的原因。"use strict"指令会渐渐成为过去,因为ECMAScript的模块和类都会自动在严格模式下运行,而无需另外指定,这意味着这些情况下不再需要使用"use strict"。但是,在极少数情况下,你如果需要一个带有非简单参数的函数在严格模式下运行,可以使用IIFE模式创建函数:

const doSomething = (function() {
    "use strict";

    return function(value=42) {
        return value;
    };
}());

在这段代码中,函数创建在以严格模式运行的IIFE中。这允许返回的函数在使用默认参数值的情况下以严格模式运行。因为外部作用域是以严格模式运行的,所以解析默认参数值是不会出错的,并且在函数体中不需要额外使用"use strict"

总结

禁止有非简单参数列表的函数的函数体使用"use strict"指令,ECMAScript 2016 这一小的改变,也表现出这样一门流行的编程语言的发展历程是多么坎坷。在这样的情况下,TC-39决定引入一个新的语法错误来消除歧义,如果上述问题出现的早的话,这也可能已经是ECMAScript 6(2015)的一部分了。添加这个语法错误是最明智的选择,因为它对现有的代码影响甚微(规范更改是与JavaScript引擎实现非简单参数列表同步的),而且不大可能影响以后的JavaScript代码,因为ECMAScript的模块和类是以严格模式运行的。

参考文献

  1. Static Semantics: IsSimpleParameterList (ecma-international.org)
  2. It's time to start using JavaScript strict mode (nczonline.net)
  3. The scope of "use strict" with respect to destructuring in parameter lists

免责声明:本文中表达的任何观点和看法都是Nicholas C. Zakas的观点和看法,并且不以任何方式反映我的雇主,我的同事,Wrox出版社O'Reilly出版社或任何其他人的观点和看法。 我仅代表个人的观点,与他人无关。