附录B:理解ES7(ES2016)
ES6 的开发消耗了大约四年时间,此后 TC-39 小组决定不再接受如此长的开发过程,他们改用年度周期来发布语言的新特性,以确保语言特性能够尽快发展。
更频繁的发布意味着每个新的 ES 版本拥有的新特性会比 ES6 少得多。为了标示这种变化,新规范的版本号不再重点描述版本数字,而改为指明规范发布的年份。因此 ES6 也被称为 ES2015 ,而 ES7 也正式被称为 ES2016 。 TC-39 小组预计将来所有的 ES 版本都会使用这个以年份为基础的命名系统。
ES2016 在 2016 年 3 月定稿,并且只向语言添加了三项内容:一个新的数学运算符、一个新的数组方法,以及一种新的语法错误。这些全都包含在本附录中。
幂运算符
ES2016 对 JS 语法引入的唯一改进就是幂运算符( exponentiation operator ),此数学运算能将指数运用到底数上。 虽然已经有 Math.pow()
方法可用于幂运算,然而 JS 也是在此方面只能调用方法而不能使用运算符的少数语言之一,并且一些开发者认为运算符可读性更强、更易于检查。
幂运算符是两个星号(**
),其左侧是底数,右侧则是指数,例如:
let result = 5 ** 2;
console.log(result); // 25
console.log(result === Math.pow(5, 2)); // true
此例计算了 52 ,结果为 25 。你也可以使用 Math.pow()
来获取相同结果。
运算顺序
幂运算符的优先级在 JS 的二元运算符中是最高的(但低于一元运算符)。这意味着在复合运算中它会被优先运用,正如下例:
let result = 2 * 5 ** 2;
console.log(result); // 50
52 的计算会首先发生,其结果值随后会与 2 相乘来产生最终的结果: 50 。
操作数的限制
与其他运算符不同,幂运算符有一个稍显独特的限制:运算符左侧不能是除了 ++
或 --
之外的任意一元表达式,例如,下面的语法是无效的:
// 语法错误
let result = -5 ** 2;
本例中的 -5
是个语法错误,因为运算的顺序是有二义性的。 -
应当运用到 5
上面,还是运用到 5 ** 2
的结果上面呢?禁止左侧的一元表达式消除了这个歧义。为了清晰地说明意图,需要给 -5
或 5 ** 2
添加圆括号,如下所示:
// 没问题
let result1 = -(5 ** 2); // 等于 -25
// 也没问题
let result2 = (-5) ** 2; // 等于 25
若你为幂运算表达式包裹括号,那么 -
会作用在这整个表达式上;而若你为 -5
包裹括号,则清楚表示想要获取 -5 的平方。
在幂运算符的左侧表达式使用的是 ++
或 --
时,就无需使用括号,因为这两个运算符在操作数上的行为都被清晰定义了: ++
或 --
作为前缀会在其他任意运算发生之前修改操作数,而作为后缀则会在整个表达式计算完毕后才修改操作数。在幂运算符左侧的这两种用法都是安全的,正如下面代码所示:
let num1 = 2,
num2 = 2;
console.log(++num1 ** 2); // 9
console.log(num1); // 3
console.log(num2-- ** 2); // 4
console.log(num2); // 1
此例中的 num1
在幂运算符被运用之前就已被递增,因此 num1
会变为 3 ,并且幂运算的结果为 9 。而对于 num2
来说,在参与幂运算时它的值仍然保持为 2 ,在运算之后才被递减到 1 。
Array.prototype.includes() 方法
你或许会记得 ES6 新增了 String.prototype.includes()
用于检查某个子字符串是否存在于指定字符串中。 ES6 起初也想引入一个 Array.prototype.includes()
方法,让使用类似方式处理字符串与数组的倾向能得以保持。但此方法的规范未能赶上 ES6 的最后期限,于是最终它就进入了 ES2016 。
如何使用 Array.prototype.includes()
Array.prototype.includes()
方法接受两个参数:需要搜索的值、可选的搜索起始位置索引。当提供了第二个参数时, includes()
会从该位置开始尝试匹配(默认的起始位置为 0 )。若在数组中找到了该值,返回 true
;否则返回 false
。例如:
let values = [1, 2, 3];
console.log(values.includes(1)); // true
console.log(values.includes(0)); // false
// 从索引 2 开始搜索
console.log(values.includes(1, 2)); // false
此处使用 1
去调用 values.includes()
返回了 true
,而使用 0
则会返回 false
,因为 0
并不在此数组中。当使用了第二个参数让搜索从索引位置 2 (该位置的元素值是 3
)开始进行时, values.includes()
方法返回了 false
,因为数值 1
并不存在于位置 2 与数组末端之间。
值比较
includes()
方法使用 ===
运算符来进行值比较,仅有一个例外: NaN
被认为与自身相等,尽管 NaN === NaN
的计算结果为 false
。这与 indexOf()
方法的行为不同,后者在比较时严格使用了 ===
运算符。为了明白其中的差异,研究如下代码:
let values = [1, NaN, 2];
console.log(values.indexOf(NaN)); // -1
console.log(values.includes(NaN)); // true
values.indexOf()
方法为 NaN
返回了 -1
,尽管 NaN
实际上被包含在 values
数组中。另一方面,由于使用不同的比较运算符, values.includes()
方法则为 NaN
返回了 true
。
若只想检查某个值是否存在于数组中,而不想知道它的位置,我推荐使用
includes()
,这是由于includes()
与indexOf()
方法对于NaN
的处理不同。而若确实想知道某个值在数组中的位置,那么就必须使用indexOf()
方法。
在实现中的另一个怪异点是 +0
和 -0
被认为是相等的。在这个方面, indexOf()
与 includes()
行为一致:
let values = [1, +0, 2];
console.log(values.indexOf(-0)); // 1
console.log(values.includes(-0)); // true
此处的 indexOf()
与 includes()
都在传入 -0
的时候找到了 +0
,因为它们认为这两个值是相等的。注意这与 Object.is()
方法的行为有差异,后者认为 +0
与 -0
是不同的值。
函数作用域严格模式的改动
当严格模式在 ES5 中被引入时, JS 语言要比后来的 ES6 简单得多。尽管如此, ES6 仍然允许你使用 "use strict"
指令来指定严格模式,可以在全局作用域上,让所有代码运行在严格模式下;或用在函数作用域内,只有该函数会运行在严格模式下。后一种方式最终在 ES6 中成为了一个问题,这是由于参数可以用更加复杂的方式来定义,特别是在带有解构或默认值的情况下。为了理解这个问题,研究如下代码:
function doSomething(first = this) {
"use strict";
return first;
}
此处的具名参数 first
被赋予了一个默认值 this
,而你预期 first
的值会是什么? ES6 规范指示 JS 引擎此时要用严格模式来处理参数,因此 this
的值应当等于 undefined
。然而,当函数内部指定了 "use strict"
时, 要将参数也限制在严格模式中有相当的困难,因为参数默认值也可能是个函数。这种困难情况导致大部分 JS 引擎都没有实现这个特性(因此 this
的值可能等于全局对象)。
由于该实现的难度, ES2016 规定如果函数的参数被进行解构或是拥有默认值,则在该函数内部使用 "use strict"
指令将是违法的。当函数体内出现了 "use strict"
时,该函数只允许使用简单参数列表(也就是所包含的参数没有进行解构,也没有默认值)。下面有一些示例:
// 没有问题,使用了简单参数列表
function okay(first, second) {
"use strict";
return first;
}
// 语法错误
function notOkay1(first, second=first) {
"use strict";
return first;
}
// 语法错误
function notOkay2({ first, second }) {
"use strict";
return first;
}
你依旧可以在只有简单参数列表的函数内使用 "use strict"
,这也是 okay()
函数如预期正常工作的原因(就像在 ES5 中一样)。 notOkay1()
函数则会有语法错误,因为它使用了参数的默认值。类似的, notOkay2()
函数同样有语法错误,因为它使用了解构参数。
总的来说,这项改动既解决了 JS 开发者的困惑,又消除了 JS 引擎的一个实现难题。