函数的扩展
函数参数的默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
上面代码检查函数log
的参数y
有没有赋值,如果没有,则指定默认值为World
。这种写法的缺点在于,如果参数y
赋的值对应的布尔值为false
,则该赋值不起作用。
function greet(name, greeting) {
name = (typeof name !== 'undefined') ? name : 'Student';
greeting = (typeof greeting !== 'undefined') ? greeting : 'Welcome';
return `${greeting} ${name}!`;
}
greet(); // Welcome Student!
greet('James'); // Welcome James!
greet('Richard', 'Howdy'); // Howdy Richard!
为了避免这个问题,通常需要先判断一下参数y
是否被赋值,如果没有,再等于默认值。
if (typeof y === 'undefined') {
y = 'World';
}
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
剩余参数
剩余参数也用三个连续的点 ( ... ) 表示,使你能够将不定数量的元素表示为数组。
与解构赋值结合
剩余参数可以与解构赋值结合起来,用于生成数组。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
可以将剩余参数看着展开运算符的对立面;如果展开运算符是拿出包装盒中的所有物品,那么剩余参数就是将所有物品放回包装盒中。
可变参数函数
剩余参数的另一个用例是处理可变参数函数。可变参数函数是接受不定数量参数的函数。
例如,假设有一个叫做 sum()
的函数,它会计算不定数量的数字的和。在运行期间,如何调用 sum()
函数?
sum(1, 2);
sum(10, 36, 7, 84, 90, 110);
sum(-23, 3000, 575000);
实际上有无数种方式可以调用 sum() 函数。不管传入函数的数字有多少个,应该始终返回数字的总和。
- 使用参数对象
在之前版本的 JavaScript
中,可以使用参数对象处理这种类型的函数。参数对象是像数组一样的对象,可以当做本地变量在所有函数中使用。它针对传入函数的每个参数都包含一个值,第一个参数从 0
开始,第二个参数为 1
,以此类推。
function sum() {
let total = 0;
for(const argument of arguments) {
total += argument;
}
return total;
}
现在可以正常运行,但是存在问题:
- 如果查看
sum()
函数的定义,会发现它没有任何参数。这容易引起误导,因为我们知道sum()
函数可以处理不定数量的参数。
难以理解。
- 如果你从未使用过参数对象,那么看了这段代码后很可能会疑问参数对象来自何处。是不是凭空出现的?看起来肯定是这样。
- 使用剩余参数
function sum(...nums) {
let total = 0;
for(const num of nums) {
total += num;
}
return total;
}
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
箭头函数
ES6 引入了一种新的函数,叫做箭头函数。箭头函数和普通函数的行为非常相似,但是在语法构成上非常不同。
普通函数可以是函数声明或函数表达式,但是箭头函数始终是表达式。实际上,它们的全称是“箭头函数表达式”
//普通函数
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) {
return name.toUpperCase();
});
//箭头函数
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
name => name.toUpperCase()
);
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return
语句返回。
var sum = (num1, num2) => { return num1 + num2; }
箭头函数中的this
对于普通函数,this 的值基于函数如何被调用。对于箭头函数,this 的值基于函数周围的上下文。换句话说,箭头函数内的,this 的值与函数外面的 this 的值一样。
我们先看一个普通函数中的 this 示例,再看一个箭头函数是如何使用 this 的。
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = function() {
setTimeout(function() {
this.scoops++;
console.log('scoop added!');
}, 500);
};
const dessert = new IceCream();
dessert.addScoop();
运行上述代码后,你会认为半毫秒之后,dessert.scoops
会是1
。但并非这样:结果是NaN
这是因为传递给 setTimeout()
的函数被调用时没用到 new、call() 或 apply()
,也没用到上下文对象。意味着函数内的 this
的值是全局对象,不是 dessert
对象。实际上发生的情况是,创建了新的 scoops
变量(默认值为 undefined
),然后递增(undefined + 1
结果为 NaN
)
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = function() {
setTimeout(() => { // an arrow function is passed to setTimeout
this.scoops++;
console.log('scoop added!');
}, 0.5);
};
const dessert = new IceCream();
console.log(dessert.scoops); //1
因为箭头函数从周围上下文继承了 this 值,所以这段代码可行!
如果我们将 addScoop()
方法也改为箭头函数,你认为会发生什么?
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = () => { // addScoop is now an arrow function
setTimeout(() => {
this.scoops++;
console.log('scoop added!');
}, 0.5);
};
const dessert = new IceCream();
dessert.addScoop();
这段代码因为同一原因而不起作用,即箭头函数从周围上下文中继承了 this
值。在 addScoop()
方法外面,this
的值是全局对象。因此如果 addScoop()
是箭头函数,addScoop()
中的 this
的值是全局对象。这样的话,传递给 setTimeout()
的函数中的 this
的值也设为了该全局对象!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。