前言
學習函數新增內容,需要先了解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 函數。