公子小白

ECMAScript 2016 中你不知道的改变

原文链接: www.nczonline.net

与 ECMAScript 6(也被称为 ECMAScript 2015)相比,ECMAScript 2016 是 JavaScript 语言规范的一次小的更新。由于现在 ECMAScript 将每年发布一个新版本,因此每个版本实际上只是所有已准备好特性的一个集合。大多数文章中 ECMAScript 2016 只有两个重大的改变:

  1. 添加了取幂运算符(**)
  2. 添加了 Array.prototype.includes() 方法

这两个特性对 JavaScript 开发者有最直接的影响,可是,还有一些其他的改变经常被忽略。这些改变在我写的书 (Understanding ECMAScript 6) 中提到,可是,我仍然收到一些关于它的疑问,所以我决定在这里深入探讨一下

首先我将描述这些改变,而后我会讲述其背后的原理

改变

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;
} 

在这种情况下,参数默认值使用一个函数,这将会有更多的问题。你不得不设置默认值中的函数也运行在严格模式下。确保参数默认值表达式在严格模式下被正确的解析和理解将会很复杂。

解构的参数将导致相似的问题,因为它可能包含默认值。例如:

// 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" 指令正在开始成为 JavaScript 的历史,因为 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) 中引入一个新的语法错误来消除歧义,如果这个问题出现的早的话,它可能已经是 ECMAScript 6 (2015) 的一部分了。添加这个语法错误是最明智的因为它对现有的代码影响甚微(规范改变的同时,JavaScript 引擎就实现了 non-simple 参数列表),而且不会影响将来的代码因为 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出版社或任何其他人的观点和看法,我仅代表我自己的观点,与他人无关

最近文章