本章主題較為輕鬆,討論 JavaScript ES6 箭頭函式(Arrow Function)與一般函式的差異。

一般函式#

ES6 版本之前的 JavaScript 沒有箭頭函式,僅有一般函式。基本語法不再贅述,這裡僅針對幾項容易被忽略的特性說明。

建立#

開發者可以透過 function() 關鍵字來定義、撰寫函式:

function fn() {
  // something awesome
}

另一種常見的寫法,是宣告一個變數,等號右邊放上函式宣告:

var newFunc = function fn() {
  // something awesome
};

兩種寫法在使用上唯一的差異,就是第二種寫法在宣告前不能使用。

快問快答小知識:第二種寫法中的 newFunc.name 是什麼?答案是 fn,而不是 newFunc

另外還有一種透過函數建構子的宣告方法:

var func = new Function("str", "console.log(str)");
func("test");

但這種寫法無法利用 JavaScript 引擎事先解析程式並準備 EC 的特性,造成效能浪費,非常不推薦使用。

屬性#

函式在 JavaScript 中,其實是一個可被呼叫(Callable)的物件,除了可執行、擁有例如 apply()bind()call() 之類的方法外,也擁有一些函式特有的屬性:

  • name:函式的名稱。
  • length:可接收參數的數量。

這兩者是 readonly 的屬性,在函式建立後即不能被修改。

另外有兩個已從標準廢棄,但主流瀏覽器仍支援的屬性:

  • caller:呼叫函式的地方在哪,全域呼叫則回應 null
  • argument:函數執行時所接收到的參數,非執行時呼叫則回傳 null

由於已經是被廢棄的屬性,未來隨時可能會被瀏覽器放棄支援,所以開發上能不用就盡量不用。

另外,在函數執行的過程中,還有一個額外的 arguments 物件可以使用,是一個傳入參數組成的類陣列(Array-like)物件。

被廢棄的是 Function.argument,而非在函式執行時直接呼叫的 arguments 物件!

this#

this 是讓新手難以捉摸的屬性。預設情況下,函式中的 this 指向呼叫它的物件。

例如以下範例:

function whatIsThis() {
  console.log(this);
}

let obj = {
  whatIsThis: whatIsThis,
};

whatIsThis(); // window
obj.whatIsThis(); // obj

什麼時候是非預設情況呢?我們可以透過 apply()bind()call() 等函式,改變函式的 this

箭頭函式#

雖然口頭上都稱呼為箭頭函式(Arrow Function),但實際上他是箭頭函式運算式(Arrow Function Expression),指的是類似這樣的語法:

const arrow = (param1, param2) => {
  // do something awesome
};

由於中間的 => 形似箭頭而得名;且撰寫起來快速直覺,看起來又很潮,深得開發者的喜愛。

建立#

除了前述的語法外,在特定條件下箭頭函式的語法可以進一步簡化:

// 基本用法
const arrow1 = (param1, param2) => {
  /* ... */
};

// 僅有一個參數時,可省略括號
const arrow2 = (params) => {
  /* ... */
};

// 單一運算後直接回傳時,可省略大括號
const arrow3 = (params) => params ** 2;

另外,有一定熟悉度的開發者,可能會利用箭頭函式的特性,撰寫出如下的程式碼:

let fancy = (x) => (y) => (z) => x + y + z;

遇到多層的箭頭函式,乍看之下可能會難以理解,但其實只要關注「箭頭」的位置,右到左的反向拆解,並把省略的部分加回去:

let fancy = (x) => {
  return (y) => {
    return (z) => {
      return x + y + z;
    };
  };
};

藉由箭頭函式的各種省略,開發者可以用更短的語法寫出更精簡的程式。

屬性#

與前面說明的一般函式相同,可以取得 namelength 屬性,就不重複說明了。

this#

箭頭函式的重點特色來了:箭頭函式中的 this,會依據函式在哪裡建立而決定,而非與一般函式一樣,依照執行時被呼叫的地方決定。

來看個範例:

var tmp = "a";
var obj = {
  tmp: "b",
  func: () => console.log(this.tmp),
};
console.log(tmp); // a
obj.func(); // a

由於 obj.func 是在全域環境中建立,this 就被綁定到全域環境中;因此兩個執行結果印出來的結果都是全域變數 tmp 的值 a

一般函式用來強制綁定 this 值的 apply()bind()call(),在箭頭函式中沒有 bind(),且 apply()call() 傳入的 this 值會被忽略,使用時務必特別注意。

結語#

本章針對開發中頻繁使用的一般函式與箭頭函式做說明與比較,希望幫助讀者更進一步認識它們。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10221214