前言
学习函数新增内容,需要先了解ES6的变量解构赋值。
本文大量引用阮一峰老师的ES6手册。
为函数的参数设置默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
console.log(p);
注意事项:
- 函数内部不允许给参数重复声明,比如用var、let、const声明。但可以重复赋值。
- 参数默认值不是传值的,而是每次都重新计算默认值表达式的值,即时从前计算过,也当做没计算过。也就是说,参数默认值是惰性求值的。
参数默认值跟解构赋值配合使用
首先你要懂ES6变量解构赋值。
下面是利用对象的解构赋值,函数声明的参数模式,必须与传入值的模式匹配:
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5 虽然是空对象,但是模式是匹配的
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined 传入undefined,模式不匹配,所以报错
如果想要什么参数都不传,也依然不报错,怎么做?依然是利用解构赋值,下面代码中,{x, y = 5} = {}
表示如果整个参数不存在,就默认为空对象,然后再计算x和y各是多少,x因为没有对应值,当然是undefined,y虽然也没有对应值,但是有默认值,所以y是5。
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
对比下面两段代码,它们的结果是一样的。区别在哪?
写法一:
- 没有传参,所以用默认参数,也就是空对象。
- x和y先去空对象寻找对应值,找不到,所以用自己的默认值。
写法二:
- 没有传参,所以用默认参数
{ x: 0, y: 0 }
。 - x和y去
{ x: 0, y: 0 }
寻找对应值,找到了对应值,所以直接用对应值。
区别就是在哪一步设默认。所以,在任意一步设默认都可以,只要是合法的js代码。
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
console.log(m1());
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
console.log(m2());
参数默认值应该放到参数队列最后面
这么做的目的是可以省略若干传参。如果参数默认值排在前面,没有默认值的参数反而在后面,那么传参的写法就会很不科学,比如:
function f(x, y = 5, z) {
return [x, y, z];
}
怎么传参?
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错 这种最简练的写法是错误的
f(1, undefined, 2) // [1, 5, 2] 这种写法虽然正确,但是把undefined传进去代码很丑
所以,如果参数有默认值就应该放到参数队列最后面。
参数默认值有特殊作用域
var x = 1;
// 2作为值传给x,由于y = x是完全独立的作用域,所以y的值是参数x的值,也是2
function f(x, y = x) {
console.log(y); // 打印2
}
f(2) // 2
let x = 1;
// 没有传入值,所以y取默认值,y=x形成独立作用域,所以y是x的值,x指向外层的1,也就是y是1。
function f(y = x) {
let x = 2; // 这个x不影响y的值
console.log(y); // 打印1
}
f() // 1
还有更复杂的情况,这里不多介绍了,更复杂的情况可能只会出现在面试题里,而实践中,请让自己的代码条理清晰,这样对自己,对别人,都有好处。
参数默认值的一个应用
先定义一个通用函数,不干别的,只负责报错:
function throwIfMissing() {
throw new Error('Missing parameter');
}
然后,其他函数里面如果有不允许省略的参数,就赋值为这个函数:
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(a, b, c = throwIfMissing()) {
}
foo(1,2); // Uncaught Error: Missing parameter
rest参数
学习变量解构赋值的时候,我们就遇到了rest变量,也就是:
let [a, b, ...c] = [1,2,3,4,5,6,7];
console.log(c); // [3,4,5,6,7]
参数也有这种写法,表示剩余的传参,有多少我全包了。
function add(...values) {
let sum = 0;
for (var val of values) { // values 是一个数组,包含所有传入的参数
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
所以,到现在,传参真的可以为所欲为,根本不再是ES5时代的参数必须一对一:
- 你有一系列参数,我用比如
...args
就可以打包。 - 如果你传入一个数组,我用解构赋值就可以打散。
箭头函数
箭头函数是ES6对函数写法的最大修改,改到人们一开始都不认识。
var f = v => x;
// 等价于
var f = function(v) {
return x;
};
代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => num1 + num2;
// 等价于
var sum = function(num1, num2) {
return num1 + num2;
};
由于大括号是被默认解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
箭头函数可以与变量解构结合使用:
const full = ({ first, last }) => first + ' ' + last;
// 等价于
function full({ first, last }) {
return first + ' ' + last;
}
// 使用函数的时候就full({first: 4, last: 8});就可以了
箭头函数写法的优势:
- 简练。定义一个判断是偶数的函数如下,因为这个函数就是参数跟运算,所以箭头函数很简练:
const isEven = n => n % 2 == 0;
- 简化回调函数。ES5时代,为了清洗的写回调函数,往往要折行写代码,现在就简化了。
// 正常函数写法
[1,2,3].map(function (x) {
return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);
// 正常函数写法
var result = values.sort(function (a, b) {
return a - b;
});
// 箭头函数写法
var result = values.sort((a, b) => a - b);
注意,箭头函数不是永远等价于常规写法。区别如下:
常规写法中,this对象的指向是可变的,但是在箭头函数中,它是固定的。这也是ES6为了降低js学习难度所做的改变。常规写法中,函数体内的this对象,不一定是定义时所在的对象,而是使用时所在的对象。但是,箭头写法中,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
不可以使用arguments对象,该对象在函数体内根本不存在。如果要用,可以用 rest 参数代替。
不可以使用yield命令,因此箭头函数不能用作 Generator 函数。