0%

JS特訓-DAY39-箭頭函式 (arrow function)

前言

Arrow Functions 所附帶的新特性對於函數內容的運作有著不同的行為,如果沒弄清楚,把舊函數的語法簡化成 Arrow Functions 的寫法,可能造成程式運作不正確。

Arrow Functions 小檔案

  • ECMAScript 2015 (ES6) 導入的新特性。
  • 稱為 Arrow Function Expression,或稱 Fat Arrow Functions,最初在 CoffeeScript 的語法中流行。
  • 在函數定義的語法上更為簡潔。
  • 函數運作行為上和傳統語法所定義的函數有差異,例如 argumentsthis

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 使用的注意事項

  • 沒有 Hoisting 效果

Arrow Functions 是表達式的語法形式,像傳統函數中「具名表達式」或「匿名表達式」那樣,函數定義的部分不會被 Hoist

換言之,定義必須寫在使用之前

console.log( sayHello );    //undefined
console.log( sayHello() );  // TypeError: sayHello is not a function
var sayHello = () => `Hello Bob!`;
  • 建議使用 const 做名稱部分的宣告

雖然前面例子故意都用 var,但實際上建議使用 const

因為函數表達式應該被視為一個常數的值,而非變數,函數定義這件事並不是一個該變動的東西。

W3Schools :
A function expression is always constant value.


Arrow Functions 和傳統函數語法的差異

Arrow Functions 不是單純語法簡化而已,也會對函數的運作行為多了些限制或改變。

  • 不會產生新的 arguments 物件

如果外圍包裝一層傳統函數,作用域內還是會有可用的 arguments

Arrow Functions 不會在自己的函數作用域內產生新的 arguments 物件,讓函數使用上更嚴謹。

例如以下是傳統函數寫法,雖然在函數定義的上只定義了 n1n2 兩個參數,但實際上呼叫函數時我可以丟任意個參數進去,而在函數內也可以靠 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 宣告名稱。