[TOC]
# 13. 箭頭函數
## 13.1 概覽
箭頭函數有兩個好處。
首先,沒有傳統函數那么啰嗦:
```js
const arr = [1, 2, 3];
const squares = arr.map(x => x * x);
// Traditional function expression:
const squares = arr.map(function (x) { return x * x });
```
其次,他們的`this`是從父作用域(*lexical*)中獲取的。因此,你不再需要使用`bind()` 或者 `that = this`。
```js
function UiComponent() {
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // lexical `this`
});
}
```
以下的變量在箭頭函數中屬于詞法的:
- `arguments`
- `super`
- `this`
- `new.target`
## 13.2 因為 this ,傳統的函數都是糟糕的非方法函數
在JavaScript中,傳統函數有三種類型:
1. 非方法函數
2. 方法
3. 構造函數
它們之間存在沖突: 因為第2種和第3種中, 函數總是有自己的 `this`變量。但在回調函數內部卻無法獲得其外部方法中的`this`(第1種)。
可以在下面的ES5代碼中看到:
```js
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) { // (A)
'use strict';
return arr.map(function (x) { // (B)
// Doesn’t work:
return this.prefix + x; // (C)
});
};
```
在C行,我們想訪問 `this.prefix`,但是由于行 B 的函數在作用域鏈上覆蓋了來自于行 A 方法的 `this` ,因此是無法訪問的。在嚴格模式下,非方法的函數中`this`的值為`undefined`,所以我們訪問`Prefixer`會出錯。
```js
> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
TypeError: Cannot read property 'prefix' of undefined
```
在 ECMAScript 5 中有三種解決方法。
### 13.2.1 方案 1: `that = this`
你可以將`this` 賦值給一個不會被屏蔽的變量。這就是下面行 A 所做的:
```js
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
var that = this; // (A)
return arr.map(function (x) {
return that.prefix + x;
});
};
```
現在`Prefixer` 如預期一樣工作:
```js
> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
[ 'Hi Joe', 'Hi Alex' ]
```
### 13.2.2 方案 2: 為`this`指定一個值
有很少的幾個數組方法會有額外的參數用于指定在調用回調函數的時候的 `this`值。這就是下面行 A 最后一個參數:
```js
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + x;
}, this); // (A)
};
```
### 13.2.3 方案3:`bind(this) `
可以使用方法 bind() 轉換一下在調用的時候才決定的 this 值(通過 call() ,函數調用,方法調用,等等),將其變為固定的值。這就是下面行 A 所做的:
```js
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + x;
}.bind(this)); // (A)
};
```
### 13.2.4 ECMAScript 6解決方案:箭頭函數
箭頭函數非常像方案3。但是,最好將它們看作是一種不會詞法上屏蔽`this`的新函數。也就是說,它們與常規函數不同(甚至可以說它們做的更少)。它們不是一般的函數加上綁定。
使用箭頭函數,示例代碼如下:
```js
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map((x) => {
return this.prefix + x;
});
};
```
如果要把上述代碼完全 ES6 化,就要使用類和更加緊湊多樣的箭頭函數:
```js
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
prefixArray(arr) {
return arr.map(x => this.prefix + x); // (A)
}
}
```
在行 A ,調整了一下箭頭函數兩邊的部分,節省了幾個字符:
* 如果只有一個參數,并且這個參數是一個標識符,那么可以省略小括號。
* 在箭頭后面跟一個表達式會返回該表達式的值。
## 13.3 箭頭函數語法
選用“胖”箭頭`=>`(相對于瘦箭頭`->`)的原因是與 CoffeeScript 兼容,兩者非常類似。
指定參數:
~~~
() => { ... } // no parameter
x => { ... } // one parameter, an identifier
(x, y) => { ... } // several parameters
~~~
指定函數體:
```js
x => { return x * x } // block
x => x * x // expression, equivalent to previous line
```
這些語句塊表現得像普通的函數體。例如,需要用 `return` 返回一個值。如果只有一個表達式,那么這個表達式的值會被隱式返回。
注意,只有一個表達式的箭頭函數省略了多少啰嗦的東西。比較:
```js
const squares = [1, 2, 3].map(function (x) { return x * x });
const squares = [1, 2, 3].map(x => x * x);
```
### 13.3.1 省略單個參數的括號
只有在包含單個標識符的情況下,才能省略參數周圍的括號:
```js
> [1,2,3].map (x => 2 * x)
[2,4,6]
```
其他情況下,你必須輸入括號,即使只有一個參數。例如,如果您重構了單個參數,則需要括號:
```js
> [[1,2], [3,4]].map(([a,b]) => a + b)
[ 3, 7 ]
```
如果單個參數具有默認值,則需要括號(`undefined`會觸發默認值!):
```js
> [1, undefined, 3].map((x='yes') => x)
[ 1, 'yes', 3 ]
```
## 13.4 詞匯變量
### 13.4.1 傳播變量值:靜態的和動態的
以下是可以傳播變量值的兩種方式。
首先,靜態地(詞法上的):變量可訪問的方式由程序的結構決定。在作用域內聲明的變量在其內部嵌套的所有作用域內都是可訪問的(除非被屏蔽)。例如:
```js
const x = 123;
function foo(y) {
return x; // value received statically
}
```
第二,動態地:可以通過函數調用傳播變量值。例如:
```js
function bar(arg) {
return arg; // value received dynamically
}
```
### 13.4.2 箭頭函數中的詞法變量
this 的來源是一個區分箭頭函數的重要方面:
* 傳統函數有一個動態的 this ,它的值取決于函數如何調用。
* 箭頭函數有一個詞法的 this ,它的值取決于父作用域。
從詞法上來確定值的[全部變量列表](http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions-runtime-semantics-evaluation)是:
~~~
arguments
super
this
new.target
~~~
## 13.5 語法陷阱
有一些與語法有關的細節有時會讓你出錯
### 13.5.1 箭頭功能的綁定非常松散
如果你把`=>`看作一個操作符,你可以說它的優先級較低,因為它是松散綁定的。也就是說:如果它可能會與其他操作符產生沖突,通常其他操作符會比其優先級高。
因為JS允許表達體“連接在一起”,所以以下代碼會難理解且產生問題:
```js
const f = x => (x % 2) === 0 ? x : 0;
```
換句話說,如果我們想要`===`和`?`優先級高,而`=>`比較低。我們希望它被解釋如下:
```js
const f = x => ((x % 2) === 0 ? x : 0);
```
如果希望`=>`優先級高,就像這樣:
```js
const f = (x => (x % 2)) === 0 ? x : 0;
```
如果`=>`優先級比`?`高、比`===`低,它會是這樣的:
```js
const f = (x => ((x % 2) === 0)) ? x : 0;
```
因此,如果與其他運算符競爭,則必須將括號中的箭頭函數括起來。例如:
```js
console.log(typeof () => {}); // SyntaxError
console.log(typeof (() => {})); // OK
```
另一方面,可以使用 `typeof`作為一個表達式體,不用放在大括號中:
```js
const f = x => typeof x;
```
### 13.5.2 箭頭函數參數后面不能進行換行
ES6 禁止在參數定義和箭頭函數的箭頭之間換行:
```js
const func1 = (x, y) // SyntaxError
=> {
return x + y;
};
const func2 = (x, y) => // OK
{
return x + y;
};
const func3 = (x, y) => { // OK
return x + y;
};
const func4 = (x, y) // SyntaxError
=> x + y;
const func5 = (x, y) => // OK
x + y;
```
在參數定義內進行換行是允許的:
```js
const func6 = ( // OK
x,
y
) => {
return x + y;
};
```
這種限制換行的理由是,為了保留該選項,因為可能在未來會使用“無頭”箭頭函數(即:當您定義了沒有參數的箭頭函數時,您可以省略括號)。
### 13.5.3 不能用語句作為表達式體
#### 13.5.3.1表達式與語句
快速復習(參閱《 Speaking JavaScript 》獲取更多[相關信息](http://speakingjs.com/es5/ch07.html#expr_vs_stmt)):
表達式產生(執行得到)值。例子:
```js
3 + 4
foo(7)
'abc'.length
```
語句做一些操作。例子:
```js
while (true) { ··· }
return 123;
```
大多數表達式可以被用作語句,簡單地將它們放在語句的位置上:
```js
function bar() {
3 + 4;
foo(7);
'abc'.length;
}
```
#### 3.5.3.2 箭頭函數體
如果箭頭函數體是一個表達式,那么就可以不需要花括號了:
```js
asyncFunc.then(x => console.log(x));
```
但是,語句一定要放在花括號里面:
```js
asyncFunc.catch(x => { throw x });
```
### 13.5.4 返回對象字面量
JavaScript語法的一些部分是不明確的。以下列代碼為例。
```js
{
bar: 123
}
```
它可能是:
* 具有單個屬性`bar`的對象字面量。
* 具有標簽`bar`和表達式語句`123`的塊。
假定一個箭頭函數的函數體可以是一個表達式或一個語句。如果你想讓函數體是一個對象字面量,就必須將其放在圓括號中:
```js
> const f1 = x => ({ bar: 123 });
> f1()
{ bar: 123 }
```
為了比較,這里的箭頭函數的函數體是一個塊:
```js
> const f2 = x => { bar: 123 };
> f2()
undefined
```
## 13.6 立即調用箭頭函數
還記得[立即調用函數表達式( IIFEs )](###SJ—第16章)嗎?在 ECMAScript 5 中用于模擬塊級作用域和值返回塊,看起來像下面這樣:
```js
(function () { // open IIFE
// inside IIFE
})(); // close IIFE
```
如果您使用**立即調用的箭頭函數**(**IIAF**),可以節省字符:
```js
(() => {
return 123
})();
```
### 13.6.1 分號
和 IIFEs 類似,應該在 IIAFs 結尾加上分號(或者使用一個等價的措施),以避免兩個連續的 IIAFs 被解釋成一個函數調用(第一個是函數,第二個是參數)。
### 13.6.2 帶有塊體的括號內的箭頭函數
即使IIAF有一個塊體,也必須用括號括起來,因為它不能被(直接地)函數調用。這種語法約束的原因是與箭頭函數的一致性,這些函數的主體是表達式(如下所述)。
因此,括號必須括起箭頭函數。相比之下,你可以選擇IIFEs——你可以用括號括起整個表達式:
```js
(function () {
···
}());
```
或者,只是用括號括起函數表達式:
```js
(function () {
···
})();
```
考慮到箭頭函數的工作方式,現在起,我們應該優先考慮第二種的立即調用方式。
### 13.6.3 帶有表達式體的括號內的箭頭函數
如果您想了解為什么不能通過在其后面加上括號來調用箭頭函數,則必須了解表達式體的工作方式:表達式體之后的括號應該是表達式的一部分,而不是整個箭頭函數的調用。這與前面的小節中解釋的箭頭函數松綁定有關。
看一個例子:
```js
const value = () => foo();
```
應該被解釋為這樣:
```js
const value = () => (foo());
```
而不是:
```js
const value = (() => foo)();
```
進一步閱讀:[12. ECMAScript 6中的可調用實體](22.md),里面有著關于ES6 中IIFEs 和 IIAFs的更多信息。透露一下:你會很少使用它們,因為ES6有更好的選擇。
## 13.7 箭頭函數與bind()
ES6箭頭函數通常是`Function.prototype.bind()`的令人關注的替代方案。
### 3.7.1 提取方法
如果提取的方法是作為一個回調,你必須指定一個固定的`this`,否則它將被作為函數進行調用(`this`將是`undefined`或為全局對象)。例如:
```js
obj.on('anEvent', this.handleEvent.bind(this));
```
另一種方法是使用箭頭函數:
```js
obj.on('anEvent', event => this.handleEvent(event));
```
### 13.7.2 `this`通過參數
以下代碼演示了一個簡單的技巧:對于某些方法,比如`filter()`,您不需要`bind()`這樣的回調,因為它們允許您通過附加參數指定`this`值:
```js
const as = new Set([1, 2, 3]);
const bs = new Set([3, 2, 4]);
const intersection = [...as].filter(bs.has, bs);
// [2, 3]
```
但是,如果使用箭頭函數,此代碼更容易理解:
```js
const as = new Set([1, 2, 3]);
const bs = new Set([3, 2, 4]);
const intersection = [...as].filter(a => bs.has(a));
// [2, 3]
```
### 13.7.3 部分求值(Partial evaluation)
`bind()`使您能夠進行部分求值,您可以通過填充現有函數的參數來創建新函數:
```js
function add(x, y) {
return x + y;
}
const plus1 = add.bind(undefined, 1);
```
再次,我發現箭頭函數更容易理解:
```js
const plus1 = y => add(1, y);
```
## 13.8 箭頭函數與常規函數
一個箭頭函數與一個普通的函數在兩個方面不一樣:
* 下列變量的構造是詞法的: `arguments` , `super` , `this` , `new.target`
* 不能被用作構造函數:沒有內部方法 `[[Construct]]` (該方法允許普通的函數通過 `new` 調用),也沒有 `prototype` 屬性。因此, `new (() => {})` 會拋出錯誤。
除此之外,箭頭函數和普通的函數沒有明顯的區別。例如,`typeof` 和 `instanceof` 產生同樣的結果:
```js
> typeof (() => {})
'function'
> () => {} instanceof Function
true
> typeof function () {}
'function'
> function () {} instanceof Function
true
```
參閱[12. ECMAScript 6中的可調用實體](22.md)的那一章,獲取更多關于什么時候使用箭頭函數和什么時候使用傳統函數的信息。
## 13.9 常見問題:箭頭函數
### 13.9.1 為什么ES6 中有“胖”箭頭函數(`=>`),但沒有“瘦”箭頭函數(`->`)?
ECMAScript 6 有具有詞法(靜態的)`this`的函數語法,即所謂的箭頭函數。但是,具有動態`this`的函數沒有箭頭語法。這種遺漏是經過考慮的; 方法定義涵蓋了大部分瘦箭頭的用例。如果你真的需要動態的this,你仍然可以使用傳統的函數表達式。
- 關于本書
- 目錄簡介
- 關于這本書你需要知道的
- 序
- 前言
- I 背景
- 1. About ECMAScript 6 (ES6)
- 2. 常見問題:ECMAScript 6
- 3. 一個JavaScript:在 ECMAScript 6 中避免版本化
- 4. 核心ES6特性
- II 數據
- 5. New number and Math features
- 6. 新的字符串特性
- 7. Symbol
- 8. Template literals
- 第9章 變量與作用域
- 第10章 解構
- 第11章 參數處理
- III 模塊化
- 12. ECMAScript 6中的可調用實體
- 13. 箭頭函數
- 14. 除了類之外的新OOP特性
- 15. 類
- 16. 模塊
- IV 集合
- 17. The for-of loop
- 18. New Array features
- 19. Maps and Sets
- 20. 類型化數組
- 21. 可迭代對象和迭代器
- 22. 生成器( Generator )
- V 標準庫
- 23. 新的正則表達式特性
- 24. 異步編程 (基礎知識)
- 25. 異步編程的Promise
- VI 雜項
- 26. Unicode in ES6
- 27. 尾部調用優化
- 28 用 Proxy 實現元編程
- 29. Coding style tips for ECMAScript 6
- 30. 概述ES6中的新內容
- 注釋
- ES5過時了嗎?
- ==個人筆記==