Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

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

這個寫法不是箭頭函式喔,它只是縮寫的傳統函式而已 👀

以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫