[譯]JS箭頭函數(shù)三連問(wèn):為何用魄懂、怎么用沿侈、何時(shí)用
在現(xiàn)代JS中最讓人期待的特性就是關(guān)于箭頭函數(shù),用=>來(lái)標(biāo)識(shí)市栗。箭頭函數(shù)有兩個(gè)主要的優(yōu)點(diǎn):其一是非常簡(jiǎn)明的語(yǔ)法缀拭,另外就是直觀的作用域和this的綁定。
因?yàn)檫@些優(yōu)點(diǎn)填帽,箭頭函數(shù)比起其他形式的函數(shù)聲明更加受歡迎蛛淋。比如,受歡迎的airbnb eslint configuration庫(kù)會(huì)強(qiáng)制使用JavaScript箭頭函數(shù)創(chuàng)建匿名函數(shù)篡腌。
然而褐荷,就像世間萬(wàn)物一樣,箭頭函數(shù)有一些優(yōu)點(diǎn)也有一些“缺點(diǎn)”嘹悼,這就需要在使用的時(shí)候做一些權(quán)衡了叛甫。
學(xué)習(xí)如何權(quán)衡是使用好箭頭函數(shù)的關(guān)鍵。在這篇文章中我們將回顧箭頭函數(shù)是怎樣工作的杨伙,然后深入探討其监,實(shí)際代碼中箭頭函數(shù)是如何改進(jìn)我們代碼的,以及一些箭頭函數(shù)不推薦的情況限匣。
什么才是箭頭函數(shù)
JS的箭頭函數(shù)大概就像python中的lambda(python定義匿名函數(shù)的關(guān)鍵字)和ruby中的blocks(類似于閉包)一樣抖苦。 這些匿名函數(shù)都有他們特殊的語(yǔ)法:首先接收一定數(shù)目的參數(shù),然后在定義它們的函數(shù)的作用域或就近作用域中執(zhí)行膛腐。
接下來(lái)我們將詳細(xì)探討這些睛约。
箭頭函數(shù)的語(yǔ)法
箭頭函數(shù)有一個(gè)大體的結(jié)構(gòu),同時(shí)也有很多的特殊情況可以簡(jiǎn)化哲身。 核心的結(jié)構(gòu)如下:
(argument1, argument2, ... argumentN) => {
// function body
}
在括號(hào)里面有一系列的參數(shù)辩涝,接著跟著一個(gè)箭頭符號(hào)=>,最后是函數(shù)體勘天。這跟傳統(tǒng)的函數(shù)很相像怔揩,只是我們省略了function關(guān)鍵字,并且添加了一個(gè)=>在參數(shù)后面脯丝。
并且商膊,這里也有很多種情況,讓箭頭函數(shù)結(jié)構(gòu)變得更加的簡(jiǎn)潔宠进。
首先晕拆,如果函數(shù)體里面是一個(gè)單獨(dú)的表達(dá)式,你可以省略大括號(hào)直接將表達(dá)式寫在一行材蹬,并且表達(dá)式的結(jié)果也將會(huì)被函數(shù)直接返回实幕。比如:
const add = (a, b) => a + b;
其次吝镣,如果這傳入的是一個(gè)單獨(dú)的參數(shù),你也可省略參數(shù)部分的括號(hào)昆庇。比如:
const getFirst = array => array[0];
如你所見末贾,這樣就看起來(lái)更加的簡(jiǎn)潔了,我們也將在后面說(shuō)明更多的特性整吆。
高級(jí)語(yǔ)法
如果你了解這些高級(jí)語(yǔ)法之后將十分受用拱撵。
首先,如果你嘗試在一行書寫函數(shù)表蝙,但是返回的值卻是一個(gè)對(duì)象內(nèi)容拴测,你原想這樣寫:
(name, description) => {name: name, description: description};
而問(wèn)題就是這樣的語(yǔ)法會(huì)引起歧義,會(huì)誤以為你在寫一個(gè)函數(shù)的函數(shù)體勇哗。 如果想返回的是單個(gè)的對(duì)象昼扛,請(qǐng)用括號(hào)包裝該對(duì)象:
(name, description) => ({name: name, description: description});
封閉的上下文作用域
不像其他形式的函數(shù),箭頭函數(shù)并沒有他們自己的執(zhí)行上下文欲诺。實(shí)際上抄谐,這就意味著代碼中的this和arguments都是繼承自他們的父函數(shù)。
比如扰法,比較下面箭頭函數(shù)和傳統(tǒng)函數(shù)的區(qū)別:
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);
};
}
};
我們有一個(gè)有兩個(gè)方法的對(duì)象蛹含,每個(gè)方法都返回了一個(gè)匿名函數(shù)。區(qū)別在于第一個(gè)方法里面用了傳統(tǒng)的函數(shù)表達(dá)式塞颁,后面的用了箭頭函數(shù)表達(dá)式浦箱。如果我們?cè)趥魅胪瑯拥膮?shù)運(yùn)行,我們得到了兩個(gè)不同的結(jié)果祠锣。
const anon = test.createAnonFunction('hello', 'world');
//返回匿名函數(shù)
const arrow = test.createArrowFunction('hello', 'world');
anon();
//undefined
//{}
// this->window
arrow();
//test object
//object { '0': 'hello', '1': 'world' }
//this->test
第一個(gè)匿名函數(shù)有自己的上下文(指向并非test對(duì)象)酷窥,當(dāng)你調(diào)用的時(shí)候沒有參考的this.name的屬性,(注意:現(xiàn)在this指向window)伴网,也沒有創(chuàng)建它時(shí)調(diào)用的參數(shù)蓬推。另一個(gè),箭頭函數(shù)與創(chuàng)建它的函數(shù)有相同的上下文澡腾,讓其可以訪問(wèn)參數(shù)arguments和對(duì)象沸伏。
箭頭函數(shù)改進(jìn)您的代碼
傳統(tǒng)lambda函數(shù)的主要用例之一,就是將函數(shù)用于數(shù)組的遍歷动分,現(xiàn)在用JavaScript箭頭函數(shù)實(shí)現(xiàn)毅糟。 比如你有一個(gè)有值的數(shù)組,你想去map遍歷每一項(xiàng)澜公,這時(shí)箭頭函數(shù)是非常推薦的:
const words = ['hello', 'WORLD', 'Whatever'];
const downcasedWords = words.map(word => word.toLowerCase());
一個(gè)及其常見的例子就是返回一個(gè)對(duì)象的某個(gè)值:
const names = objects.map(object => object.name);
類似的姆另,當(dāng)用forEach來(lái)替換傳統(tǒng)for循環(huán)的時(shí)候,實(shí)際上箭頭函數(shù)會(huì)直觀的保持this來(lái)自于父一級(jí)
this.examples.forEach(example => {
this.runExample(example);
});
Promise和Promise鏈
當(dāng)在編寫異步程序的時(shí)候,箭頭函數(shù)也會(huì)讓代碼更加直觀和簡(jiǎn)潔蜕青。
Promise可以更簡(jiǎn)單的編寫異步程序苟蹈。雖然你樂意去使用async/await糊渊,你也需要好好理解promise,因?yàn)檫@是他們的基礎(chǔ)右核。
使用promise,仍然需要定義你的代碼執(zhí)行完成之后的回調(diào)函數(shù)渺绒。 這是箭頭函數(shù)的理想位置贺喝,特別是如果您生成的函數(shù)是有狀態(tài)的,同時(shí)想引用對(duì)象中的某些內(nèi)容宗兼。
this.doSomethingAsync().then((result) => {
this.storeResult(result);
});
對(duì)象轉(zhuǎn)換
箭頭函數(shù)的另一個(gè)常見而且十分有用的地方就是用于封裝的對(duì)象轉(zhuǎn)換躏鱼。 例如在Vue.js中,有一種通用模式殷绍,就是使用mapState將Vuex存儲(chǔ)的各個(gè)部分染苛,直接包含到Vue組件中。 這涉及到定義一套mappers主到,用于從原對(duì)象到完整的轉(zhuǎn)換輸出茶行,這在組件問(wèn)題中實(shí)十分有必要的。 這一系列簡(jiǎn)單的轉(zhuǎn)換,使用箭頭函數(shù)是最合適不過(guò)的登钥。比如:
export default {
computed: {
...mapState({
results: state => state.results,
users: state => state.users,
});
}
}
你不應(yīng)該使用箭頭函數(shù)的情景
這里有許多箭頭函數(shù)不推薦的場(chǎng)景畔师,這種情況之下不僅沒有幫助,而且還會(huì)造成不必要的麻煩牧牢。
首先就是對(duì)象中的方法看锉。這里有一個(gè)函數(shù)上下文的例子,對(duì)于我們理解很有幫助塔鳍。 曾經(jīng)流行一種趨勢(shì)伯铣,用class類的語(yǔ)法和箭頭函數(shù),為其自動(dòng)綁定方法轮纫。比如:事件方法可以使用腔寡,但是仍然綁定在class類中。 看起來(lái)就像下面的例子:
class Counter {
counter = 0;
handleClick = () => {
this.counter++;
}
}
在這種方法中,如果被一個(gè)點(diǎn)擊事件函數(shù)調(diào)用了蜡感,它雖然不是Counter的上下文中蹬蚁,它仍舊可以訪問(wèn)實(shí)例的數(shù)據(jù),這種方式的缺點(diǎn)不言而喻郑兴。
用這種方式的確提供了一種綁定函數(shù)的快捷方式犀斋,但是函數(shù)的表達(dá)形式多種多樣,相當(dāng)不直觀情连。如果你嘗試在原型使用這種對(duì)象叽粹,這將不利于測(cè)試,同時(shí)也會(huì)產(chǎn)生很多問(wèn)題。 相反虫几,推薦用一種常規(guī)的綁定方式锤灿,如有必要可以綁定在實(shí)例的構(gòu)造函數(shù)中:
class Counter {
counter = 0;
handleClick() {
this.counter++;
}
constructor() {
this.handleClick = this.handleClick.bind(this);
}
}
深層調(diào)用
另一種使用箭頭函數(shù)會(huì)讓你頭疼的地方,就是你去用很多函數(shù)的組合調(diào)用辆脸,尤其是函數(shù)的深層調(diào)用但校。 簡(jiǎn)單的理由跟匿名函數(shù)一樣,堆棧的追蹤很復(fù)雜啡氢。
如果你的函數(shù)僅僅在一層之下状囱,而不是深層的迭代,這倒不是什么問(wèn)題倘是。但是如果你將函數(shù)定義為箭頭函數(shù)亭枷,并且在他們之間來(lái)回調(diào)用,當(dāng)你調(diào)試bug的時(shí)候你將被代碼困惑搀崭,甚至得到如下的錯(cuò)誤信息:
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
{anonymous}()
//anonymous 匿名
有動(dòng)態(tài)上下文的函數(shù)
還有最有一種箭頭函數(shù)會(huì)讓你困惑的情形叨粘,就是this是動(dòng)態(tài)綁定的時(shí)候。 如果你在以下情形使用箭頭函數(shù)瘤睹,那么this的動(dòng)態(tài)綁定不會(huì)如期工作升敲,并且你也會(huì)困惑這些代碼為什么不像預(yù)期那樣工作,也會(huì)給你之后工作的人造成麻煩默蚌。 一些典型的例子:
事件的調(diào)用函數(shù)冻晤,this指向當(dāng)前的目標(biāo)屬性
在jquery中,大多數(shù)時(shí)候this指向的是當(dāng)前被選擇的元素
在vue中绸吸,methods和computed中的this指向的是vue的組件鼻弧。
當(dāng)然你也可以在上面的情形之下謹(jǐn)慎的使用箭頭函數(shù)。但特別是在jquery和vue的情況下, 這通常會(huì)干擾正常功能, 并使您感到困惑:為什么看起來(lái)跟別人代碼一樣的代碼就是不工作锦茁。
總結(jié)
箭頭函數(shù)是JS語(yǔ)言中十分特別的屬性攘轩,并且使很多情形中代碼更加的變化莫測(cè)。盡管如此码俩,就像其他的語(yǔ)言特性度帮,他們有各自的優(yōu)缺點(diǎn)。因此我們使用它應(yīng)該僅僅是作為一種工具稿存,而不是無(wú)腦的簡(jiǎn)單的全部替換為箭頭函數(shù)笨篷。
原文:https://juejin.im/post/5be599d4e51d451d23633ec8z