前言
Arrow Functions 所附帶的新特性對於函數內容的運作有著不同的行為,如果沒弄清楚,把舊函數的語法簡化成 Arrow Functions 的寫法,可能造成程式運作不正確。
Arrow Functions 小檔案
- ECMAScript 2015 (ES6) 導入的新特性。
- 稱為 Arrow Function Expression,或稱 Fat Arrow Functions,最初在 CoffeeScript 的語法中流行。
- 在函數定義的語法上更為簡潔。
- 函數運作行為上和傳統語法所定義的函數有差異,例如
arguments、this
Arrow Functions 語法
標準語法
var add = (n1 ,n2) => {
return n1 + n2;
};
console.log( add(3, 6) ); // 9
和傳統語法比起來:
- 少了
function關鍵字。 - 使用
=>符號來告知這是 Arrow Functions。
當函數內容只有單行回傳時的簡寫
很常我們的函數內容只做很簡單的動作,就像前面的 add() 只有一行負責簡單運算並同時做 return。
語法上可以進一步簡化:
var add = (n1 ,n2) => n1 + n2;
console.log( add(3, 6) ); // 9
- 省略函數外殼
{ } - 省略
return關鍵字
當只有單一個參數時的簡寫
例如以下範例,intro() 只有一個參數 name:
var intro = (name) => {
return `Hi, I am ${name}!`;
};
console.log( intro('Bob') ); // "Hi, I am Bob!"
intro() 在參數部分的語法可以進一步簡化,省略小括號,效果一樣:
var intro = name => {
return `Hi, I am ${name}!`;
};
console.log( intro('Bob') ); // "Hi, I am Bob!"
搭配前面提到的單行回傳簡寫法,整體語法就更顯簡潔:
var intro = name => `Hi, I am ${name}!`;
console.log( intro('Bob') ); // "Hi, I am Bob!"
但記得這是「單一個參數」時的簡寫,若是沒有參數或多個參數,仍必須用標準寫法,例如以下是沒有參數的範例:
var sayHello = () => `Hello Bob!`;
console.log( sayHello() ); // "Hello Bob!"
Arrow Functions 使用的注意事項
Arrow Functions 是表達式的語法形式,像傳統函數中「具名表達式」或「匿名表達式」那樣,函數定義的部分不會被 Hoist
換言之,定義必須寫在使用之前
console.log( sayHello ); //undefined
console.log( sayHello() ); // TypeError: sayHello is not a function
var sayHello = () => `Hello Bob!`;
雖然前面例子故意都用 var,但實際上建議使用 const
因為函數表達式應該被視為一個常數的值,而非變數,函數定義這件事並不是一個該變動的東西。
W3Schools :
A function expression is always constant value.
Arrow Functions 和傳統函數語法的差異
Arrow Functions 不是單純語法簡化而已,也會對函數的運作行為多了些限制或改變。
如果外圍包裝一層傳統函數,作用域內還是會有可用的
arguments
Arrow Functions 不會在自己的函數作用域內產生新的 arguments 物件,讓函數使用上更嚴謹。
例如以下是傳統函數寫法,雖然在函數定義的上只定義了 n1 和 n2 兩個參數,但實際上呼叫函數時我可以丟任意個參數進去,而在函數內也可以靠 arguments 取得所有參數:
const add = function (n1, n2) {
console.log(arguments); // Arguments(3) [100, 200, 300]
return n1 + n2;
};
console.log( add(100, 200, 300) ); //300
甚至極端一點,我可以完全不管參數定義什麼:
const add = function (n1, n2) {
return arguments[0] + arguments[1];
};
console.log( add(100, 200, 300) ); //300
這讓函數的參數定義非常沒有約束力。看起來好像很自由,但這種寫法很容易導致「包裝與內容物不符合」,會讓程式難以維護。
而 Arrow Functions 改善這一點。在 Arrow Functions 內不會為這個作用域建立一個新的 arguments 物件,因此 arguments 不再被認得:
const add = (n1, n2) => {
console.log(arguments); // ReferenceError: arguments is not defined
return n1 + n2;
};
console.log( add(100, 200, 300) ); // 300
無法使用 arguments,代表只能取用被定義的參數。雖然依舊無法阻止呼叫端任意亂丟參數進來,但至少會讓函數內容更可控。
但要注意,Arrow Functions 只是不會產生新的 arguments,不代表 arguments 變數一定不存在。
例如以下例子,使用一個傳統函數包裝一個 Arrow Function:
function getObj(){
console.log(arguments); // Arguments(3) [1, 2, 3]
return {
f: () => {
console.log(arguments); // Arguments(3) [1, 2, 3]
}
};
}
getObj(1, 2, 3).f(4, 5);
- 傳統函數
getObj()還是會產生新的arguments。 - 對作用域來說,
f()可以使用getObj()內存在的變數。 - 因此如果在
f()內去使用arguments,會取到的是getObj()的arguments,要特別注意。
如果使用 Arrow Function 去包裝另一個 Arrow Function:
var getObj = () => {
console.log(arguments); // ReferenceError: arguments is not defined
return {
f: () => {
console.log(arguments); // ReferenceError: arguments is not defined
}
};
}
getObj(1, 2, 3).f(4, 5);
getObj()和f()都不會產生新的arguments。因此在
getObj()和f()內企圖使用arguments,都會得到arguments is not defined的錯誤。this 運作行為的不同
判斷 this 代表什麼物件的大原則:看呼叫時的物件是誰
例如借用函數的例子 (函數被定義在物件之外),雖然 whatsThis 語彙上定義的地方是在 Global Context,但被執行時的呼叫者是 player,因此回傳的 this 物件是 player:
var whatsThis = function() {
return this;
};
var player = {};
player.f = whatsThis;
console.log(player.f() === player); // true
但如果是 Arrow Functions 就不一樣了:
var whatsThis = () => {
return this;
};
var player = {};
player.f = whatsThis;
console.log(player.f() === player); // false
console.log(player.f() === window); // true
可以發現,同樣的呼叫方式,this 不再回傳呼叫者物件,在這個例子變成回傳 Global 物件,是不同的運作行為。
Arrow Functions 重點小結
- 是 ES6 導入的新特性。
- 在語法上更為簡潔。
- 不是單純語法簡化而已,也會對函數的運作行為多了些限制或改變:
- 函數執行時不會產生新的
arguments物件。 this的運作方式與傳統函數不同。
- 函數執行時不會產生新的
- 定義的語法必須在使用之前 (不具 Hoisting 效果)。
- 建議使用
const宣告名稱。