JavaScript 箭頭函數:適用與不適用場景


JavaScript 箭頭函數:適用不適用場景

現代 JavaScript 中最引人注目的功能之一是引入了箭頭函數,用 => 來標識。

 

這種函數有兩大優點 – 非常簡潔的語法,和更直觀的作用域和 this的綁定。

 

這些優點有時導致箭頭函數比其他形式的函數聲明更受歡迎。

 

例如,受歡迎的 airbnb eslint 配置 會在您創建匿名函數時強制使用JavaScript箭頭函數。

 

然而,就像工程中的任何東西一樣,箭頭函數優點很明顯,同時也帶來了一些負面的東西。 使用他們的時候需要權衡一下。

 

學習如何權衡是更好地使用箭頭函數的關鍵。

 

在本文中,我們將首先回顧箭頭函數的工作原理,然后深入研究箭頭函數改進代碼的示例,最后深入研究箭不建議使用頭函數的示例。

 

JavaScript 箭頭函數究竟是什么?

JavaScript 箭頭函數大致相當於 python 中的 lambda 函數 或 Ruby 中的 blocks

 

這些是匿名函數,它們有自己的特殊語法,接受一定數量的參數,並在其封閉的作用域的上下文(即定義它們的函數或其他代碼)中操作。

 

讓我們依次分解這些部分。

 

箭頭函數語法

箭頭函數具有單一的總體結構,然后在特殊情況下可以通過多種方式簡化它們。 核心結構如下所示:

 

(argument1, argument2, ... argumentN) => {

  // function body

}

括號內的是參數列表,后跟“胖箭頭”(=>),最后是函數體。

 

這與傳統函數非常相似,我們只是省略 function 關鍵字並在參數后添加一個胖箭頭(=>)。

 

然而,有許多方法可以簡化箭頭函數。

 

首先,如果函數體是單個表達式,則可以不使用花括號並將其置於內聯中(省略大括號直接將表達式寫在一行中)。 表達式的結果將由函數返回。 例如:

 

const add = (a, b) => a + b;

其次,如果只有一個參數,你甚至可以省略參數的括號。例如:

 

const getFirst = array => array[0];

正如您所看到的,這是一些非常簡潔的語法,我們將重點介紹后面的好處。

 

高級語法

有一些高級語法可以了解一下。

 

首先,如果您嘗試使用內聯單行表達式語法,但您返回的值是對象字面量。您可能會認為這看起來應該是這樣的:

 

(name, description) => {name: name, description: description};

問題是這種語法比較含糊不清,容易引起歧義 看起來好像你正試圖創建一個傳統的函數體。 如果你碰巧想要一個對象的單個表達式,請用括號包裹該對象:

 

(name, description) => ({name: name, description: description});

封閉的上下文作用域

與其他形式的函數不同,箭頭函數沒有自己的 執行期上下文。

 

實際上,這意味着 this arguments 都是從它們的父函數繼承而來的。

 

例如,使用和不使用箭頭函數比較以下代碼:

 

const test = {

  name: 'test object',

  createAnonFunction: function() {

    return function() {

      console.log(this.name);

      console.log(arguments);

    };

  },

 

  createArrowFunction: function() {

    return () => {

      console.log(this.name);

      console.log(arguments);

    };

  }

};

我們有一個簡單的 test 對象,有兩個方法 – 每個方法都返回一個匿名函數。

 

不同之處在於第一個方法使用傳統函數表達式,而后者中使用箭頭函數。

 

如果我們使用相同參數,在控制台中運行它們,我們會得到完全不一樣的結果。

 

> const anon = test.createAnonFunction('hello', 'world');

> const arrow = test.createArrowFunction('hello', 'world');

 

> anon();

undefined

{}

 

> arrow();

test object

{ '0': 'hello', '1': 'world' }

第一個匿名函數有自己的函數上下文,因此當您調用它時,test 對象的 this.name 沒有可用的引用,也沒有創建它時調用的參數。

 

另一個,箭頭函數具有與創建它的函數完全相同的函數上下文,使其可以訪問 argumetns test 對象。

 

使用箭頭函數改進您的代碼

傳統 lambda 函數的主要用例之一,就是用於遍歷列表中的項,現在用 JavaScript 箭頭函數實現。

 

比如你有一個有值的數組,你想去 map 遍歷每一項,這時使用箭頭函數非常理想:

 

const words = ['hello', 'WORLD', 'Whatever'];

const downcasedWords = words.map(word => word.toLowerCase());

一個非常常見的例子是提取對象中的某個特定值:

 

const names = objects.map(object => object.name);

類似地,當用現代迭代樣式取代傳統的 for 循環,一般我們使用 forEach 循環,箭頭函數能夠保持 this 來自於父級,讓他們非常直觀

 

類似的,當用 forEach 來替換傳統 for循環的時候,實際上箭頭函數會直觀的保持 this來自於父一級

 

this.examples.forEach(example => {

  this.runExample(example);

});

Promises Promise

箭頭函數的另一個可以使代碼更清晰,更直觀的地方是管理異步代碼。

 

Promises 使得管理異步代碼變得容易很多(即使你很歡快地使用 async / await,你仍然應該理解 async / await 是建立在 Promises 之上的 !)

 

但是,雖然使用 promises 仍然需要定義在異步代碼或調用完成后運行的函數。

 

這是箭頭函數的理想位置,特別是如果您生成的函數是有狀態的,同時想引用對象中的某些內容。 例如:

 

this.doSomethingAsync().then((result) => {

  this.storeResult(result);

});

對象轉換

箭頭函數的另一個常見且極其強大的用途是封裝對象轉換。

 

例如,在 Vue.js 中,有一種通用模式,用於使用 mapState Vuex 存儲的各個部分直接包含到 Vue 組件中。

 

這涉及定義一組mappers” ,這些 “mappers” 將從原始的完整的 state 對象轉換為提取所涉及組件所需的內容。

 

這些簡單的轉換使用箭頭函數再合適不過了。比如:

 

export default {

  computed: {

    ...mapState({

      results: state => state.results,

      users: state => state.users,

    });

  }

}

你不應該使用箭頭函數的情景

在許多情況下,使用箭頭函數不是一個好主意。 他們不僅不會幫助你,而且會給你帶來一些不必要的麻煩。

 

第一個是對象的方法。 這是一個函數上下文的例子,這對於我們理解很有幫助。

 

有一段時間使用 Class(類)屬性語法和箭頭函數的組合,作為創建“自動綁定方法”的方式,例如, 事件處理程序可以使用,但仍然綁定到類的方法。

 

這看起來像是這樣的:

 

class Counter {

  counter = 0;

 

  handleClick = () => {

    this.counter++;

  }

}

這樣,即使 handleClick 由事件處理程序調用,而不是在 Counter 實例的上下文中調用,它仍然可以訪問實例的數據。

 

這種方法的缺點很多,在本文中很好地記錄。

 

雖然使用這種方法確實為您提供了具有綁定函數的快捷方式,但該函數以多種不直觀的方式運行,如果您嘗試將此對象作為原型進行子類化/使用,則會不利於測試,同時也會產生很多問題。

 

相反,使用常規函數,如果需要,將其綁定到構造函數中的實例:

 

class Counter {

  counter = 0;

 

  handleClick() {

    this.counter++;

  }

 

  constructor() {

    this.handleClick = this.handleClick.bind(this);

  }

}

深層的調用鏈

箭頭函數可能讓你遇到麻煩的另一個地方是,它們被用於許多不同的組合,特別是在函數深層調用鏈中。

 

核心原因與匿名函數相同 – 它們給出了非常糟糕的堆棧跟蹤。

 

如果你的函數只是向下一級,比如在迭代器里面,那也不是太糟糕,但是如果你把所有的函數定義為箭頭函數,並在它們之間來回調用,你就會陷入困境 遇到一個錯誤的時候,只是收到錯誤消息,如:

 

{anonymous}()

{anonymous}()

{anonymous}()

{anonymous}()

{anonymous}()

有動態上下文的函數

箭頭函數可能讓您遇到麻煩的最后一種情況就是嗎, this 是動態綁定的時候。

 

如果您在這些位置使用箭頭函數,那么動態綁定將不起作用,並且你(或稍后使用你的代碼的其他人)可能會對事情未按預期執行的原因感到困惑。

 

一些典型的例子:

 

事件處理程序是通過將 this 設置為事件的 currentTarget 屬性來調用。

如果您仍在使用 jQuery ,則大多數 jQuery 方法將 this 設置為已選擇的 dom 元素。

如果您正在使用 Vue.js ,則方法和計算函數通常將 this 設置為 Vue 組件。

當然你可以故意使用箭頭函數來覆蓋這種行為,但特別是在 jQuery Vue 的情況下,這通常會干擾正常運行,讓你感到困惑的是為什么看起來與附近其他代碼相同的代碼不起作用。

 

總結

箭頭函數是 JavaScript 語言的一個非常有必要的補充,並且在許多情況下使代碼更符合人們的閱讀習慣。

 

然而,像所有其他特性一樣,它們有優點和缺點。 我們應該將它們作為我們工具箱中的另一個工具,而不是作為所有函數的全面替代品。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM