箭頭函數和常規函數之間的 5 個區別

做者:Dmitri Pavlutin

翻譯:瘋狂的技術宅javascript

原文:https://dmitripavlutin.com/di...前端

在 JavaScript 中,你能夠經過多種方式去定義函數。java

第一種經常使用的方法是使用關鍵字 functiongit

// 函數聲明
function greet(who) {
  return `Hello, ${who}!`;
}
// 函數表達式
const greet = function(who) {
  return `Hello, ${who}`;
}

代碼中的函數聲明和函數表達式被稱爲「常規函數」。程序員

從 ES2015 開始,第二種可用的方法是 箭頭函數 語法:github

const greet = (who) => {
  return `Hello, ${who}!`;
}

雖然二者的語法都可以定義函數,可是在開發時該怎麼選擇呢?這是個好問題。面試

在本文中,我將展現二者之間的主要區別,以供你可以根據須要選擇正確的語法。express

1. this

1.1常規函數

在常規 JavaScript 函數內部,this 值(即執行上下文)是動態的。segmentfault

動態上下文意味着 this 的值取決於如何調用函數。在 JavaScript 中,有 4 種調用常規函數的方式。數組

簡單調用過程當中,this 的值等於全局對象(若是函數在嚴格模式下運行,則爲 undefined ):

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

// 簡單調用
myFunction(); // logs global object (window)

方法調用過程當中,this 的值是擁有該方法的對象:

const myObject = {
  method() {
    console.log(this);
  }
};
// 方法調用
myObject.method(); // logs "myObject"

在使用 myFunc.call(context, arg1, ..., argN)myFunc.apply(context, [arg1, ..., argN]) 的間接調用中,this 的值等於第一個參數:

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

const myContext = { value: 'A' };

myFunction.call(myContext);  // logs { value: 'A' }
myFunction.apply(myContext); // logs { value: 'A' }

在使用關鍵字 new 的構造函數調用期間,this 等於新建立的實例:

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

new MyFunction(); // logs an instance of MyFunction

1.2箭頭函數

箭頭函數中 this 的行爲與常規函數的 this 行爲有很大不一樣。

不管如何執行或在何處執行,箭頭函數內部的 this 值始終等於外部函數的 this 值。換句話說,箭頭函數可按詞法解析 this,箭頭函數沒有定義本身的執行上下文。

在如下示例中,myMethod() 是箭頭函數 callback() 的外部函數:

const myObject = {
  myMethod(items) {
    console.log(this); // logs "myObject"    
    const callback = () => {
      console.log(this); // logs "myObject"    
    };
    items.forEach(callback);
  }
};

myObject.myMethod([1, 2, 3]);

箭頭函數 callback() 中的 this 值等於外部函數 myMethod()this

this 詞法解析是箭頭函數的重要功能之一。在方法內部使用回調時,要確保箭頭函數沒有定義本身的 this:再也不有 const self = this 或者 callback.bind(this) 這種解決方法。

2.構造函數

2.1 常規函數

如上一節所述,常規函數能夠輕鬆的構造對象。

例如用 Car() 函數建立汽車的實例:

function Car(color) {
  this.color = color;
}

const redCar = new Car('red');
redCar instanceof Car; // => true

Car 是常規函數,使用關鍵字 new 調用時會建立 Car 類型的新實例。

2.2 箭頭函數

this 詞法解決了箭頭函數不能用做構造函數。

若是你嘗試調用帶有 new 關鍵字前綴的箭頭函數,則 JavaScript 會引起錯誤:

const Car = (color) => {
  this.color = color;
};

const redCar = new Car('red'); // TypeError: Car is not a constructor

調用 new Car('red')(其中 Car 是箭頭函數)會拋出 TypeError: Car is not a constructor

3. arguments 對象

3.1 常規函數

在常規函數的主體內部,arguments 是一個特殊的相似於數組的對象,其中包含被調用函數的參數列表。

讓咱們用 3 個參數調用 myFunction 函數:

function myFunction() {
  console.log(arguments);
}

myFunction('a', 'b'); // logs { 0: 'a', 1: 'b'}

相似於數組對象的 arguments 中包含調用參數:'a''b'

3.2箭頭函數

另外一方面,箭頭函數內部未定義 arguments 特殊關鍵字。

用詞法解析 arguments 對象:箭頭函數從外部函數訪問 arguments

讓咱們試着在箭頭函數內部訪問 arguments

function myRegularFunction() {
  const myArrowFunction = () => {    
      console.log(arguments);  
  }
  myArrowFunction('c', 'd');
}

myRegularFunction('a', 'b'); // logs { 0: 'a', 1: 'b' }

箭頭函數 myArrowFunction() 由參數 'c', 'd' 調用。在其主體內部,arguments 對象等於調用 myRegularFunction() 的參數: 'a', 'b'

若是你想訪問箭頭函數的直接參數,可使用剩餘參數 ...args

function myRegularFunction() {
  const myArrowFunction = (...args) => {    
      console.log(args);  
  }
  myArrowFunction('c', 'd');
}

myRegularFunction('a', 'b'); // logs { 0: 'c', 1: 'd' }

剩餘參數 ... args 接受箭頭函數的執行參數:{ 0: 'c', 1: 'd' }

4.隱式返回

4.1常規函數

使用 return expression 語句從函數返回結果:

function myFunction() {
  return 42;
}

myFunction(); // => 42

若是缺乏 return 語句,或者 return 語句後面沒有表達式,則常規函數隱式返回 undefined

function myEmptyFunction() {
  42;
}

function myEmptyFunction2() {
  42;
  return;
}

myEmptyFunction();  // => undefined
myEmptyFunction2(); // => undefined

4.2箭頭函數

能夠用與常規函數相同的方式從箭頭函數返回值,但有一個有用的例外。

若是箭頭函數包含一個表達式,而你省略了該函數的花括號,則將顯式返回該表達式。這些是內聯箭頭函數

const increment = (num) => num + 1;

increment(41); // => 42

increment() 僅包含一個表達式:num + 1。該表達式由箭頭函數隱式返回,而無需使用 return 關鍵字。

5. 方法

5.1 常規函數

常規函數是在類上定義方法的經常使用方式。

在下面 Hero 類中,用了常規函數定義方法 logName()

class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }

  logName() {    console.log(this.heroName);  }}

const batman = new Hero('Batman');

一般把常規函數用做方法。

有時你須要把該方法做爲回調提供給 setTimeout() 或事件監聽器。在這種狀況下,你可能會很難以訪問 this 的值。

例如用 logName() 方法做爲 setTimeout() 的回調:

setTimeout(batman.logName, 1000);
// after 1 second logs "undefined"

1 秒鐘後,undefined 會輸出到控制檯。 setTimeout()執行 logName 的簡單調用(其中 this 是全局對象)。這時方法會與對象分離。

讓咱們手動把 this 值綁定到正確的上下文:

setTimeout(batman.logName.bind(batman), 1000);
// after 1 second logs "Batman"

batman.logName.bind(batman)this 值綁定到 batman 實例。如今,你能夠肯定該方法不會丟失上下文。

手動綁定 this 須要樣板代碼,尤爲是在你有不少方法的狀況下。有一種更好的方法:把箭頭函數做爲類字段。

5.2 箭頭函數

感謝類字段提案(目前在第3階段),你能夠將箭頭函數用做類中的方法。

與常規函數相反,如今用箭頭定義的方法可以把 this 詞法綁定到類實例。

讓咱們把箭頭函數做爲字段:

class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }

  logName = () => {    
      console.log(this.heroName);  
  }
}

const batman = new Hero('Batman');

如今,你能夠把 batman.logName 用於回調而無需手動綁定 thislogName() 方法中 this 的值始終是類實例:

setTimeout(batman.logName, 1000);
// after 1 second logs "Batman"

6. 總結

瞭解常規函數和箭頭函數之間的差別有助於爲特定需求選擇正確的語法。

常規函數中的 this 值是動態的,並取決於調用方式。是箭頭函數中的 this 在詞法上是綁定的,等於外部函數的 this

常規函數中的 arguments 對象包含參數列表。相反,箭頭函數未定義 arguments(可是你能夠用剩餘參數 ...args 輕鬆訪問箭頭函數參數)。

若是箭頭函數有一個表達式,則即便不用 return 關鍵字也將隱式返回該表達式。

最後一點,你能夠在類內部使用箭頭函數語法定義去方法。粗箭頭方法將 this 值綁定到類實例。

無論怎樣調用胖箭頭方法,this 始終等於類實例,在回調這些方法用時很是有用。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索