「譯」JS箭頭函數三連問:為何用、怎麼用、何時用

「譯」JS箭頭函數三連問:為何用、怎麼用、何時用

在現代JS中最讓人期待的特性就是關於箭頭函數,用=>來標識。箭頭函數有兩個主要的優點:其一是非常簡明的語法,另外就是直觀的作用域和 this 的綁定。

因為這些優點,箭頭函數比起其他形式的函數聲明更加受歡迎。比如,受歡迎的airbnb eslint configuration庫會強制使用JavaScript箭頭函數創建匿名函數。

然而,就像世間萬物一樣,箭頭函數有一些優點也有一些“缺點”,這就需要在使用的時候做一些權衡了。

學習如何權衡是使用好箭頭函數的關鍵。在這篇文章中我們將回顧箭頭函數是怎樣工作的,然後深入探討,實際代碼中箭頭函數是如何改進我們代碼的,以及一些箭頭函數不推薦的情況。

什麼才是箭頭函數

JS的箭頭函數大概就像python中的lambda(python定義匿名函數的關鍵字)和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);
};
}
};

我們有一個有兩個方法的對象,每個方法都返回了一個匿名函數。區別在於第一個方法裡面用了傳統的函數表達式,後面的用了箭頭函數表達式。如果我們在傳入同樣的參數運行,我們得到了兩個不同的結果。

const anon = test.createAnonFunction('hello', 'world');
//返回匿名函數
const arrow = test.createArrowFunction('hello', 'world');
anon();
//undefined
//{}
// this->window
arrow();
//test object
//object { '0': 'hello', '1': 'world' }
//this->test

第一個匿名函數有自己的上下文(指向並非test對象),當你調用的時候沒有參考的 this.name 的屬性,(注意:現在this指向window),也沒有創建它時調用的參數。另一個,箭頭函數與創建它的函數有相同的上下文,讓其可以訪問參數arguments和對象。

箭頭函數改進您的代碼

傳統lambda函數的主要用例之一,就是將函數用於數組的遍歷,現在用JavaScript箭頭函數實現。 比如你有一個有值的數組,你想去map遍歷每一項,這時箭頭函數是非常推薦的:

const words = ['hello', 'WORLD', 'Whatever'];
const downcasedWords = words.map(word => word.toLowerCase());

一個及其常見的例子就是返回一個對象的某個值:

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

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

this.examples.forEach(example => {
this.runExample(example);
});

Promise和Promise鏈

當在編寫異步程序的時候,箭頭函數也會讓代碼更加直觀和簡潔。

Promise可以更簡單的編寫異步程序。雖然你樂意去使用async/await,你也需要好好理解promise,因為這是他們的基礎。

使用promise,仍然需要定義你的代碼執行完成之後的回調函數。 這是箭頭函數的理想位置,特別是如果您生成的函數是有狀態的,同時想引用對象中的某些內容。

this.doSomethingAsync().then((result) => {
this.storeResult(result);
});

對象轉換

箭頭函數的另一個常見而且十分有用的地方就是用於封裝的對象轉換。 例如在Vue.js中,有一種通用模式,就是使用mapState將Vuex存儲的各個部分,直接包含到Vue組件中。 這涉及到定義一套mappers,用於從原對象到完整的轉換輸出,這在組件問題中實十分有必要的。 這一系列簡單的轉換,使用箭頭函數是最合適不過的。比如:

export default {
computed: {
...mapState({
results: state => state.results,
users: state => state.users,
});
}
}

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

這裡有許多箭頭函數不推薦的場景,這種情況之下不僅沒有幫助,而且還會造成不必要的麻煩。

首先就是對象中的方法。這裡有一個函數上下文的例子,對於我們理解很有幫助。 曾經流行一種趨勢,用 class 類的語法和箭頭函數,為其自動綁定方法。比如:事件方法可以使用,但是仍然綁定在class類中。 看起來就像下面的例子:

class Counter {
counter = 0;
handleClick = () => {
this.counter++;
}
}

在這種方法中,如果被一個點擊事件函數調用了,它雖然不是Counter的上下文中,它仍舊可以訪問實例的數據,這種方式的缺點不言而喻。

用這種方式的確提供了一種綁定函數的快捷方式,但是函數的表達形式多種多樣,相當不直觀。如果你嘗試在原型使用這種對象,這將不利於測試,同時也會產生很多問題。 相反,推薦用一種常規的綁定方式,如有必要可以綁定在實例的構造函數中:

class Counter {
counter = 0;
handleClick() {
this.counter++;
}
constructor() {
this.handleClick = this.handleClick.bind(this);
}
}

深層調用

另一種使用箭頭函數會讓你頭疼的地方,就是你去用很多函數的組合調用,尤其是函數的深層調用。 簡單的理由跟匿名函數一樣,堆棧的追蹤很複雜。

如果你的函數僅僅在一層之下,而不是深層的迭代,這倒不是什麼問題。但是如果你將函數定義為箭頭函數,並且在他們之間來回調用,當你調試bug的時候你將被代碼困惑,甚至得到如下的錯誤信息:

{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
//anonymous 匿名

有動態上下文的函數

還有最有一種箭頭函數會讓你困惑的情形,就是this是動態綁定的時候。 如果你在以下情形使用箭頭函數,那麼this的動態綁定不會如期工作,並且你也會困惑這些代碼為什麼不像預期那樣工作,也會給你之後工作的人造成麻煩。 一些典型的例子:

  • 事件的調用函數,this指向當前的目標屬性
  • 在jquery中,大多數時候this指向的是當前被選擇的元素
  • 在vue中, methods 和 computed 中的 this 指向的是vue的組件。

當然你也可以在上面的情形之下謹慎的使用箭頭函數。但特別是在jquery和vue的情況下, 這通常會干擾正常功能, 並使您感到困惑:為什麼看起來跟別人代碼一樣的代碼就是不工作。

總結

箭頭函數是JS語言中十分特別的屬性,並且使很多情形中代碼更加的變化莫測。儘管如此,就像其他的語言特性,他們有各自的優缺點。因此我們使用它應該僅僅是作為一種工具,而不是無腦的簡單的全部替換為箭頭函數。

需要前端資料或者想學前端的小夥伴可以私信“前端”“前端資料”免費獲取


分享到:


相關文章: