ES6 學習箭頭函式

箭頭函式基本架構

1
2
3
4
5
6
7
8
9
10
11
// 正常結構
;([param], [param]) => {
statements
}

// 縮寫結構
param => expression

// param(parameter) 參數
// statements 陳述句
// expression 表達式

延伸閱讀:重讀 Axel 的 Javascript 中的 Expression vs Statement 一文

傳統函式與箭頭函式寫法差異

1
2
3
4
5
6
7
8
// 傳統函式
var callSomeoneToEat = function(someone) {
return someone + ',吃飯了!'
}
// 箭頭函式 - 省略function字眼,在小括號後加上=>
var callSomeoneToEat = someone => {
return someone + ',吃飯了!'
}

箭頭函式 - 縮寫

只可用在 function code 只有一行的情況。

1
2
3
4
5
6
7
// 1. "省略大括號 + return"。
// 注意:省略了程式還是會自動return。
var callSomeoneToEat = someone => someone + ',吃飯了!'

// 2. 可以再省略"小括號"。
// 注意:如果`沒有傳入參數`或有`兩個以上參數`,都不能省略小括號。
var callSomeoneToEat = someone => someone + ',吃飯了!'

註:雖然很多情況可以省略圓括號(),但建議你一律都用圓括號框起來。(Google 5.5.3, eslint: arrow-parens)

補充

1
2
3
4
5
6
// 若返回值或參數有包含{},像是物件內容,需要在外圍加上括號()
var foo = () => ({ x: 10, y: 20 })
var foo = ({ x, y }) => x + y

// 非常適合簡化回調函數 (callback function)
arr.map(num => num * 10) // arr.map((num) => { return num * 10)}

箭頭函式沒有 Arguments 物件

傳統函式
傳統函式在執行環境被創造的時候,JavaScript 引擎會自動生成一個 arguments 物件。

1
2
3
4
5
const foo = function() {
console.log(arguments)
}

foo(10, 20, 30, 40, 50)

箭頭函式
ES6 的箭頭函式不會產生Arguments類物件,若要取用參數,需要利用Spread - 展開與其餘參數這篇所提到的其餘參數方法來代替。

1
2
3
4
5
6
// 錯誤示範
const foo = () => {
console.log(arguments) // Uncaught ReferenceError: arguments is not defined
}

foo(10, 20, 30, 40, 50)
1
2
3
4
5
6
// ES6其餘參數
const foo = (...arg) => {
console.log(arg) // Array [10, 20, 30, 40, 50]
}

foo(10, 20, 30, 40, 50)

This 綁定差異

參考資料

傳統函式 箭頭函式
this值是動態的,由呼叫這個函式的物件(Owner)決定 this是由箭頭函式本身所在的詞彙環境所決定,也就是他所在的函式作用域(function scope)的this就是他的this,其他塊級作用域(block scope/if、for..等的)不會影響他的this

在 ES6 之後,JS 引進了一種新式的設計,稱為 Lexical this(詞彙環境的 this),下面簡單的說明一下。

以傳統函式而言,在 JS 中的預設 this 值,就是呼叫這個函式的物件,例如是 obj.method(),除非 method 是一個綁定方法(一般指 bind 這個方法),不然 this 預設就是 obj。

箭頭函式的預設 this 就是用 Lexical this(詞彙環境的 this),它雖然是函式,但它不像傳統函式的設計,在被呼叫時以呼叫它的物件作為預設 this 值,或是在全域呼叫時以 window 或全域物件作為預設 this (或是嚴格模式下是 undefined )。它不是這樣設計的,它是從詞彙環境(Lexical enviroment)中捕捉 this 值,作為自己的預設 this 值

★ 會影響 Lexical this( 詞彙環境的 this )的只有函數的作用域(function scope),包含函式或全域的作用域。其他的塊級作用域(block scope,例如 if、 for..等等)並不會影響箭頭函式的 this。

上面講了一堆,實際看例子就會很容易理解,這是一個在函式中使用箭頭函式的例子:
更多例子 - 箭頭函數的 this

1
2
3
4
5
6
7
8
9
10
11
const obj = {}

function func() {
setTimeout(() => {
console.log(this)
}, 1000)
}

func.call(obj) // {obj} - func的this被以call語法指定在obj物件,所以箭頭函式會根據他所在的作用域(func)的this為自己的this

func() // window - 這裡func的this指向window,同理箭頭函式也一樣會指向window

在以往這個有名的常見問題中,解決方式有很多種,要不就是要開發者自己作”捕捉”外層函式 this 的動作,或是用 bind 方法自己綁定,下面這是其中一種用”捕捉”外層函式 this 的解決方式:

1
2
3
4
5
6
7
8
9
10
const obj = {}

function func() {
const self = this // 這裡用一個常數self先捕捉到this,讓它到作用域鏈上
setTimeout(function() {
console.log(self)
}, 1000)
}

func.call(obj) // {obj}

類似的問題非常多,你可以從 setTimeout 的例子就可以看到,這種用了 callback(回調, 回呼)作為傳入參數的方法,在 JS 中多到一個程度,語法也是很常見,可見在真實的應用情況,如果能像箭頭函式能作週邊的捕抓 this 值,作為自己的預設 this,可以得到多大的方便與減少多少的濳在問題。

當然,箭頭函式的 Lexical this(詞彙的 this)作用在大部的情況都可以運作得很好,但它也有在某些下不適合使用的情況,因為 Lexical this(詞彙的 this)一旦綁定過了,就無法再覆蓋,即使是用 new 關鍵字也不行。不過,這部份可能會比較進階,你可以參考 You Don’t Know JS: this & Object Prototypes 與這篇文章中的內容。也因為如此,有些情況下你不應該使用箭頭函式,在下面的內容中會說明。


不可使用箭頭函式的情況

物件中的方法

因為箭頭函式會以物件在定義時的捕捉到的週邊 this 為預設 this,下例的箭頭函式並沒有函數包覆,而他只受function scope影響,因此他會離開 obj 物件的 scope,往外找到全域環境,我們都知道在全域環境執行 console.log(this) 會得到 window,這也成為此例箭頭函式捕捉到的 this 參考,所以此例的 this 指向的是 window(或是在嚴格模式的 undefined)。

1
2
3
4
5
6
7
8
const obj = {
//..
method: () => {
console.log(this)
}
}

obj.method() // window

不可搭配 apply, call, bind

this 在 Arrow function 中是被綁定的(參考周邊函數的 this 的),所以套用 call 的方法時是無法修改 this 的

1
2
3
4
5
6
7
8
9
10
11
let family = {
ming: '小明'
}
const func = () => {
console.log(this)
}
const func2 = function() {
console.log(this)
}
func.call(family) // 箭頭函式的情況,this 依然是 window
func2.call(family) // 一般函示 this 則是傳入的物件

不能當作建構子

由於 this 的是在物件下建立,所以箭頭函式不能像 function 一樣作為建構式的函式,如果嘗試使用此方法則會出現錯誤 (... is not a constructor)

1
2
3
4
5
6
var Construcor = () => {
this.name = 'Dylan'
this.age = 18
}

var literal = new Construcor()

DOM 事件監聽

同先前說的, this 是指向所建立的物件上,如果是用在監聽 DOM 上一樣會指向 window,所以無法使用在此情境。

1
2
3
4
5
6
7
8
var el = document.getElementsByTagName('div');
var changeDOM = () => {
console.log(this); // 指向 window Object
this.style.border = '1px solid red'. // 錯誤
}
for (var i = 0; i < el.length; i++) {
el[i].addEventListener('click', changeDOM, false);
}

Prototype 中使用 this

一樣是 this 的問題,如果原型上新增一個箭頭函式,並嘗試使用 this 的話會指向全域。

1
2
3
4
5
6
7
8
9
10
11
12
function construc() {
// ...
}
construc.prototype.fn = function(someone) {
console.log(this)
}
construc.prototype.fn2 = someone => {
console.log(this)
}
const obj = new construc()
obj.fn() // {construc}
obj.fn2() // window

資料來源

JavaScript 中的 this
六角學院 - Vue 出一個電商網站