展开运算符展开运算符(Spread operator)在某些方面与剩余参数的概念正好相反。剩余参数将会收集传入某个给定调用的一些可选值,展开运算符获取一个值数组并 “展开” 它们,基本上讲,就是解构它们以用作被调用的函数的各个参数。
展开运算符的最简单用例是将各个元素串联到一个数组中:
清单 6. 使用展开运算符进行串联let arr1 = [0, 1, 2];
let arr2 = [...arr1, 3, 4, 5];
console.log(arr2); // prints 0,1,2,3,4,5
|
如果没有展开运算符语法,您需要提取第一个数组中的每个元素并附加到第二个数组,然后才添加剩余元素。
也可以在函数调用中使用展开运算符;事实上,这是您最有可能使用它的地方:
清单 7. 函数调用中的展开运算符function printPerson(first, last, age) {
console.log(first, last age);
}
let args = ["Ted", "Neward", 45];
printPerson(...args);
|
请注意,不同于剩余参数,展开运算符是在调用点上使用,而不是在函数定义中使用。
函数语法和语义除了参数更改之外,ECMAScript 6 在函数语法和语义上也进行了重大改动。本节将介绍最重要的更新。只需记住,JavaScript 程序中的原始语法仍然可行。如果您最初感觉这种新语法不方便或不够直观,您可以逐步适应它的使用。
箭头函数随着 Scala 和 F# 等新函数语言被大众接受,旧语言已开始采用它们的一些优秀功能。其中一项功能是箭头函数语法,这是一种用于创建函数字面量的速记符号。从 ECMAScript 6 开始,您可以使用所谓的粗箭头(与细箭头相对)创建函数字面量,就像这样:
清单 8. 创建函数字面量的箭头语法let names = ["Ted","Jenni","Athen"];
names.forEach((n) => console.log(n));
|
如果尝试过使用 C#、Java 8、Scala 或 F# 进行函数编程,您可能非常熟悉这种语法。即使您不熟悉它,箭头函数也很容易理解:箭头前的括号将参数捕获到函数主体,箭头本身表示函数主题的开头。如果主体仅包含一条语句或表达式,则不需要使用花括号。如果主体包含多条语句或表达式,那么可以通过在箭头后输入花括号来表示它们:
清单 9. 表示多条语句或表达式let names = ["Ted","Jenni","Athen"];
names.forEach((n) => {
console.log(n)
});
|
如果只有一个参数,您可以选择完全省略括号,如下所示。(就个人而言,我甚至在只有一个参数时也使用括号,但这只是仁者见仁,智者见智的的审美偏好。)
清单 10. 单个参数names.forEach(n => console.log(n));
|
箭头函数不能直接取代函数关键字。一般而言,您应该继续使用 function 定义方法(即与一个对象实例关联的函数)。为与对象无关的场景保留箭头函数,比如 Array.forEach 或 Array.map 调用的主体。因为箭头函数对待 this 的方式与普通函数不同,所以在方法定义中使用它们可能导致意料之外的结果。
另请注意,如果箭头函数的主体是只有一个值的单个表达式,则无需显式返回,而是应该将单一表达式隐式返回给箭头函数的调用方。但是,如果主体不只一条语句或表达式,则必须使用花括号,而且所有返回的值都必须通过常用的 “return” 语法发回给调用方。
‘this’ 的新定义在开始设计 ECMAScript 6 之前很长一段时间,程序员很难确定 ECMAScript 的 this 参数指向哪里。从表面上看,与其他 C 族语言一样,this 参数引用的对象上会调用一个方法,如下所示:
清单 11. ‘this’ 引用一个对象实例let bob = {
firstName: "Bob",
lastName: "Robertson",
displayMe: function() {
for (let m in this) {
console.log(m,"=",this[m]);
}
}
};
bob.displayMe();
|
上面的参数显然引用了实例 bob,而且忠实地打印出 firstName、lastName 和 displayMe 方法(因为它也是该对象的成员)的名称和值。
当从一个存在于全局范围的函数引用 this 时,情况会变得有点怪异:
清单 12. ‘this’ 引用一个全局范围对象let displayThis = function() {
for (let m in this) {
console.log(m);
}
};
displayThis();
|
对于缺乏经验的开发人员,ECMAScript 将全局范围定义为一个对象,所以当在全局范围内的函数使用时,this 引用全局范围对象,在上面的情况中,它忠实地打印出全局范围的每个成员,包括顶级全局变量、函数和对象(比如上面的示例中的 “console”)。
出于这个原因,我们也可以在两种不同的上下文中重用该函数,知道它每次将或多或少执行一些我们期望的操作:
清单 13. 重用全局范围函数let displayThis = function() {
for (let m in this) {
console.log(m);
}
};
displayThis(); // this == global object
let bob = {
firstName: "Bob",
lastName: "Robertson",
displayMe: displayThis
};
bob.displayMe(); // this == bob
|
可能此语法有点奇怪,但只要您理解了规则,就不是问题。直到您尝试使用 ECMAScript 构造函数作为对象类型时,情况才会真正偏离主题:
清单 14. 一个过度使用的 ‘this’function Person() {
// The Person() constructor defines "this" as an instance
// of itself
this.age = 0;
setInterval(function growUp() {
// In non-strict mode, the growUp() function defines "this"
// as the global object; thus, "this.age" refers to a global
// "age" value, not the one defined on the instance of Person
this.age++;
}, 1000);
}
var p = new Person();
// Every second, p.age is supposed to go up by one.
// But because the "this" in the "growUp" function literal
// refers to the global object, and not "p", p.age will
// never change from 0. |