JavaScript ES6 箭頭函式與傳統函式的差異
本文內容主要探討 JavaScript ES6 中的「箭頭函式」的相關概念。
差別一:更短的函式寫法
把傳統函式的 function
拿掉,再加上箭頭 =>
就會變成箭頭函式。
1// 傳統函式 2var callSomeone = function (someone) { 3 return someone + '吃飯了'; 4}; 5 6// 箭頭函式 7var callSomeone = (someone) => { 8 return someone + '吃飯了'; 9}; 10 11console.log(callSomeone('小明')); // "小明吃飯了" 12
如果函式的 Code 只有一行 return ...
,則可以再省略大括號 {}
和 return
,改為只有一行的形式。
1var callSomeone = (someone) => someone + '吃飯了'; 2console.log(callSomeone('小明')); // "小明吃飯了" 3
另外,參數只有一個的時候 (someone)
的括號其實可以省略,但如果是不帶入參數的情況就不能省略,所以個人習慣一律都不省略。
差別二:沒有 arguments 參數,請善用其餘參數
傳統函式預設的 arguments
參數是一個類陣列 (Array-like) 物件,它的值就是我們傳進函式的所有參數。
但是 ES6 的箭頭函式沒有 arguments
參數,如果我們呼叫它只會出現 arguments is not defined
的錯誤訊息。
類陣列 (Array-like) 是指一樣有
length
這項屬性,以及索引從 0 開始等特性,但是沒有陣列內建的方法像是forEach()
或是map()
。
1// 傳統函式可以使用 arguments 參數 2const updateEasyCard = function (five) { 3 console.log(five); // 5 4 console.log(arguments); // Arguments(4) [5, 10, 15, 20, callee: ƒ, Symbol(Symbol.iterator): ƒ] 5}; 6updateEasyCard(5, 10, 15, 20); 7
如果想要在箭頭函式中像是 arguments
參數那樣大量使用參數的話,可以使用之前其餘參數來幫忙做到,而且其餘參數會組出一個真正的陣列,我們可以在它身上使用各種陣列方法。
1// 箭頭函式使用其餘參數達到 arguments 參數的效果 2const updateEasyCard = (five, ...arg) => { 3 console.log(five); // 5 4 console.log(arg); // (3) [10, 15, 20] 5}; 6updateEasyCard(5, 10, 15, 20); 7
差別三:this 綁定的差異
- 傳統函式:綁定「使用」方法時的那個物件。
- 箭頭函式:綁定到「定義時」所在的執行環境下的那個物件。
首先,傳統函式的 this
會指向呼叫方法時的物件,範例中 callName()
與 setTimeout
都是用一般傳統函式的寫法。
另外,因為 setTimeout
完整的寫法是 window.setTimeout
,所以其實這裡呼叫 setTimeout
的是全域物件 window
。
1// 傳統函式 2var name = '全域阿婆'; 3var auntie = { 4 name: '漂亮阿姨', 5 callName: function () { 6 // this => auntie 7 console.log(`1, ${this.name}, ${this}`); // 1, 漂亮阿姨, [object Object] 8 setTimeout(function () { 9 // this => window 10 console.log(`2, ${this.name}`); // 2, 全域阿婆 11 console.log(`3, ${this}`); // 3, [object Window] 12 }, 10); 13 }, 14}; 15 16auntie.callName(); 17
現在我們試著把函式 callName
改成箭頭函式,執行後會發現裡面的 this
都是指向全域的 window
。因為箭頭函式沒有自己的 this
變數,裡面所使用的 this
的值是由上下文決定的,而不是看呼叫方法的物件。
由於在執行 auntie.callName()
之前,所有的 this
都已經被定義過了。
當 Parser 在讀程式碼、定義這些 this
的時候,所在的執行環境是全域執行環境,因此 this
就會指向定義時所在的執行環境的物件,也就是 window
物件。
1var name = '全域阿婆'; 2var auntie = { 3 name: '漂亮阿姨', 4 callName: () => { 5 console.log(`1, ${this.name}, ${this}`); // 1, 全域阿婆, [object Window] 6 // 這邊 setTimeout 就算改用傳統函式也一樣,因為外層是箭頭函式了 7 setTimeout(() => { 8 console.log(`2, ${this.name}`); // 2, 全域阿婆 9 console.log(`3, ${this}`); // 3, [object Window] 10 }, 10); 11 }, 12}; 13 14auntie.callName(); 15
常見的兼容寫法 (self)
如果我們想要用 ES6 箭頭函式又擔心 this
指向的問題,可以在一開始就指定一個變數 self
(或是其他你喜歡的變數名稱),不過外層函式依然只能是傳統函式喔。
1var auntie = { 2 name: '漂亮阿姨', 3 // 在傳統函式裡面盡情地使用箭頭函式 4 callName() { 5 // 定義 self 變數 6 var self = this; 7 // 使用箭頭函式 8 setTimeout(() => { 9 console.log(this); 10 console.log(self, self.name); 11 }, 10); 12 }, 13}; 14auntie.callName(); 15
補充一下,因為定義
setTimeout
時執行環境就是在callName()
函式執行環境下,所以其實setTimeout
裡面的this
本來就是指向auntie
物件。
不過定義self
還是可以幫助到多層的情況。
Vue 的 Methods 建議使用傳統函式
我們在寫 Vue 的 methods
時很常使用 this
去取用元件的 data
,像是 this.name
這類的寫法。
如果使用箭頭函式,在程式碼邏輯變得很複雜後,就很容易會出錯。所以在 Vue 的 methods
裡面會建議使用傳統函式與縮寫的方式來呈現。
1// 傳統函式配合縮寫,原本是 callName: function() {...} 2callName () { 3 console.log('1', this.name, this); 4 setTimeout(function () { 5 console.log('2', this.name); 6 console.log('3', this); 7 }, 10); 8 }, 9
這個寫法不是箭頭函式喔,它只是縮寫的傳統函式而已 👀
以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫