什么時(shí)候你不能使用箭頭函數(shù)酬姆?

共 2670 字,讀完需 5 分鐘奥溺。編譯自 Dmitri Pavlutin文章辞色,對(duì)原文內(nèi)容做了精簡(jiǎn)和代碼風(fēng)格優(yōu)化。ES6 中引入的箭頭函數(shù)可以讓我們寫(xiě)出更簡(jiǎn)潔的代碼谚赎,但是部分場(chǎng)景下使用箭頭函數(shù)會(huì)帶來(lái)嚴(yán)重的問(wèn)題淫僻,有哪些場(chǎng)景?會(huì)導(dǎo)致什么問(wèn)題壶唤?該怎么解決雳灵,容我慢慢道來(lái)。

能見(jiàn)證每天在用的編程語(yǔ)言不斷演化是一件讓人非常興奮的事情闸盔,從錯(cuò)誤中學(xué)習(xí)悯辙、探索更好的語(yǔ)言實(shí)現(xiàn)、創(chuàng)造新的語(yǔ)言特性是推動(dòng)編程語(yǔ)言版本迭代的動(dòng)力迎吵。JS 近幾年的變化就是最好的例子躲撰, 以 ES6 引入的箭頭函數(shù)(arrow functions)、class 等特性為代表击费,把 JS 的易用性推到了新的高度拢蛋。

關(guān)于 ES6 中的箭頭函數(shù),網(wǎng)上有很多文章解釋其作用和語(yǔ)法蔫巩,如果你剛開(kāi)始接觸 ES6谆棱,可以從這里開(kāi)始快压。任何事物都具有兩面性,語(yǔ)言的新特性常常被誤解垃瞧、濫用蔫劣,比如箭頭函數(shù)的使用就存在很多誤區(qū)。接下來(lái)个从,筆者會(huì)通過(guò)實(shí)例介紹該避免使用箭頭函數(shù)的場(chǎng)景脉幢,以及在這些場(chǎng)景下該如何使用函數(shù)表達(dá)式(function expressions)、函數(shù)聲明或者方法簡(jiǎn)寫(xiě)(shorthand method)來(lái)保障代碼正確性和可讀性嗦锐。

1. 定義對(duì)象方法

JS 中對(duì)象方法的定義方式是在對(duì)象上定義一個(gè)指向函數(shù)的屬性嫌松,當(dāng)方法被調(diào)用的時(shí)候,方法內(nèi)的 this 就會(huì)指向方法所屬的對(duì)象意推。

1.1 定義字面量方法

因?yàn)榧^函數(shù)的語(yǔ)法很簡(jiǎn)潔豆瘫,可能不少同學(xué)會(huì)忍不住用它來(lái)定義字面量方法,比如下面的例子 JS Bin

const calculator = {
    array: [1, 2, 3],
    sum: () => {
        console.log(this === window); // => true
        return this.array.reduce((result, item) => result + item);
    }
};

console.log(this === window); // => true

// Throws "TypeError: Cannot read property 'reduce' of undefined"
calculator.sum();

calculator.sum 使用箭頭函數(shù)來(lái)定義菊值,但是調(diào)用的時(shí)候會(huì)拋出 TypeError外驱,因?yàn)檫\(yùn)行時(shí) this.array 是未定義的,調(diào)用 calculator.sum 的時(shí)候腻窒,執(zhí)行上下文里面的 this 仍然指向的是 window昵宇,原因是箭頭函數(shù)把函數(shù)上下文綁定到了 window 上,this.array 等價(jià)于 window.array儿子,顯然后者是未定義的瓦哎。

解決的辦法是,使用函數(shù)表達(dá)式或者方法簡(jiǎn)寫(xiě)(ES6 中已經(jīng)支持)來(lái)定義方法柔逼,這樣能確保 this 是在運(yùn)行時(shí)是由包含它的上下文決定的蒋譬,修正后的代碼如下 JS Bin

const calculator = {
    array: [1, 2, 3],
    sum() {
        console.log(this === calculator); // => true
        return this.array.reduce((result, item) => result + item);
    }
};
calculator.sum(); // => 6

這樣 calculator.sum 就變成了普通函數(shù),執(zhí)行時(shí) this 就指向 calculator 對(duì)象愉适,自然能得到正確的計(jì)算結(jié)果犯助。

1.2 定義原型方法

同樣的規(guī)則適用于原型方法(prototype method)的定義,使用箭頭函數(shù)會(huì)導(dǎo)致運(yùn)行時(shí)的執(zhí)行上下文錯(cuò)誤维咸,比如下面的例子 JS Bin

function Cat(name) {
    this.name = name;
}

Cat.prototype.sayCatName = () => {
    console.log(this === window); // => true
    return this.name;
};

const cat = new Cat('Mew');
cat.sayCatName(); // => undefined

使用傳統(tǒng)的函數(shù)表達(dá)式就能解決問(wèn)題 JS Bin

function Cat(name) {
    this.name = name;
}

Cat.prototype.sayCatName = function () {
    console.log(this === cat); // => true
    return this.name;
};

const cat = new Cat('Mew');
cat.sayCatName(); // => 'Mew'

sayCatName 變成普通函數(shù)之后剂买,被調(diào)用時(shí)的執(zhí)行上下文就會(huì)指向新創(chuàng)建的 cat 實(shí)例。

2. 定義事件回調(diào)函數(shù)

this 是 JS 中很強(qiáng)大的特性癌蓖,可以通過(guò)多種方式改變函數(shù)執(zhí)行上下文瞬哼,JS 內(nèi)部也有幾種不同的默認(rèn)上下文指向,但普適的規(guī)則是在誰(shuí)上面調(diào)用函數(shù) this 就指向誰(shuí)租副,這樣代碼理解起來(lái)也很自然坐慰,讀起來(lái)就像在說(shuō),某個(gè)對(duì)象上正在發(fā)生某件事情用僧。

但是讨越,箭頭函數(shù)在聲明的時(shí)候就綁定了執(zhí)行上下文两残,要?jiǎng)討B(tài)改變上下文是不可能的,在需要?jiǎng)討B(tài)上下文的時(shí)候它的弊端就凸顯出來(lái)把跨。比如在客戶端編程中常見(jiàn)的 DOM 事件回調(diào)函數(shù)(event listenner)綁定,觸發(fā)回調(diào)函數(shù)時(shí) this 指向當(dāng)前發(fā)生事件的 DOM 節(jié)點(diǎn)沼死,而動(dòng)態(tài)上下文這個(gè)時(shí)候就非常有用着逐,比如下面這段代碼試圖使用箭頭函數(shù)來(lái)作事件回調(diào)函數(shù) JS Bin

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    console.log(this === window); // => true
    this.innerHTML = 'Clicked button';
});

在全局上下文下定義的箭頭函數(shù)執(zhí)行時(shí) this 會(huì)指向 window,當(dāng)單擊事件發(fā)生時(shí)意蛀,瀏覽器會(huì)嘗試用 button 作為上下文來(lái)執(zhí)行事件回調(diào)函數(shù)耸别,但是箭頭函數(shù)預(yù)定義的上下文是不能被修改的,這樣 this.innerHTML 就等價(jià)于 window.innerHTML县钥,而后者是沒(méi)有任何意義的秀姐。

使用函數(shù)表達(dá)式就可以在運(yùn)行時(shí)動(dòng)態(tài)的改變 this,修正后的代碼 JS Bin

const button = document.getElementById('myButton');
button.addEventListener('click', function() {
    console.log(this === button); // => true
    this.innerHTML = 'Clicked button';
});

當(dāng)用戶單擊按鈕時(shí)若贮,事件回調(diào)函數(shù)中的 this 實(shí)際指向 button省有,這樣的 this.innerHTML = 'Clicked button' 就能按照預(yù)期修改按鈕中的文字。

3. 定義構(gòu)造函數(shù)

構(gòu)造函數(shù)中的 this 指向新創(chuàng)建的對(duì)象谴麦,當(dāng)執(zhí)行 new Car() 的時(shí)候蠢沿,構(gòu)造函數(shù) Car 的上下文就是新創(chuàng)建的對(duì)象,也就是說(shuō) this instanceof Car === true匾效。顯然舷蟀,箭頭函數(shù)是不能用來(lái)做構(gòu)造函數(shù), 實(shí)際上 JS 會(huì)禁止你這么做面哼,如果你這么做了野宜,它就會(huì)拋出異常。

換句話說(shuō)魔策,箭頭構(gòu)造函數(shù)的執(zhí)行并沒(méi)有任何意義匈子,并且是有歧義的。比如代乃,當(dāng)我們運(yùn)行下面的代碼 JS Bin

const Message = (text) => {
    this.text = text;
};
// Throws "TypeError: Message is not a constructor"
const helloMessage = new Message('Hello World!');

構(gòu)造新的 Message 實(shí)例時(shí)旬牲,JS 引擎拋了錯(cuò)誤,因?yàn)?Message 不是構(gòu)造函數(shù)搁吓。在筆者看來(lái)原茅,相比舊的 JS 引擎在出錯(cuò)時(shí)悄悄失敗的設(shè)計(jì),ES6 在出錯(cuò)時(shí)給出具體錯(cuò)誤消息是非常不錯(cuò)的實(shí)踐堕仔±揲伲可以通過(guò)使用函數(shù)表達(dá)式或者函數(shù)聲明 來(lái)聲明構(gòu)造函數(shù)修復(fù)上面的例子 JS Bin

const Message = function(text) {
    this.text = text;
};
const helloMessage = new Message('Hello World!');
console.log(helloMessage.text); // => 'Hello World!'

4. 追求過(guò)短的代碼

箭頭函數(shù)允許你省略參數(shù)兩邊的括號(hào)摩骨、函數(shù)體的花括號(hào)通贞、甚至 return 關(guān)鍵詞朗若,這對(duì)編寫(xiě)更簡(jiǎn)短的代碼非常有幫助。這讓我想起大學(xué)計(jì)算機(jī)老師給學(xué)生留過(guò)的有趣作業(yè):看誰(shuí)能使用 C 語(yǔ)言編寫(xiě)出最短的函數(shù)來(lái)計(jì)算字符串的長(zhǎng)度昌罩,這對(duì)學(xué)習(xí)和探索新語(yǔ)言特性是個(gè)不錯(cuò)的法子哭懈。但是,在實(shí)際的軟件工程中茎用,代碼寫(xiě)完之后會(huì)被很多工程師閱讀遣总,真正的 write once, read many times,在代碼可讀性方面轨功,最短的代碼可能并不總是最好的旭斥。一定程度上,壓縮了太多邏輯的簡(jiǎn)短代碼古涧,閱讀起來(lái)就沒(méi)有那么直觀垂券,比如下面的例子 JS Bin

const multiply = (a, b) => b === undefined ? b => a * b : a * b;
const double = multiply(2);
double(3);      // => 6
multiply(2, 3); // => 6

multiply 函數(shù)會(huì)返回兩個(gè)數(shù)字的乘積或者返回一個(gè)可以繼續(xù)調(diào)用的固定了一個(gè)參數(shù)的函數(shù)。代碼看起來(lái)很簡(jiǎn)短羡滑,但大多數(shù)人第一眼看上去可能無(wú)法立即搞清楚它干了什么菇爪,怎么讓這段代碼可讀性更高呢?有很多辦法啄栓,可以在箭頭函數(shù)中加上括號(hào)娄帖、條件判斷、返回語(yǔ)句昙楚,或者使用普通的函數(shù) JS Bin

function multiply(a, b) {
    if (b === undefined) {
        return function (b) {
            return a * b;
        }
    }
    return a * b;
}

const double = multiply(2);
double(3); // => 6
multiply(2, 3); // => 6

為了讓代碼可讀性更高近速,在簡(jiǎn)短和啰嗦之間把握好平衡是非常有必要的。

5. 總結(jié)

箭頭函數(shù)無(wú)疑是 ES6 帶來(lái)的重大改進(jìn)堪旧,在正確的場(chǎng)合使用箭頭函數(shù)能讓代碼變的簡(jiǎn)潔削葱、短小,但某些方面的優(yōu)勢(shì)在另外一些方面可能就變成了劣勢(shì)淳梦,在需要?jiǎng)討B(tài)上下文的場(chǎng)景中使用箭頭函數(shù)你要格外的小心析砸,這些場(chǎng)景包括:定義對(duì)象方法、定義原型方法爆袍、定義構(gòu)造函數(shù)首繁、定義事件回調(diào)函數(shù)。

One More Thing

本文作者王仕軍陨囊,商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)弦疮,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。如果你覺(jué)得本文對(duì)你有幫助蜘醋,請(qǐng)點(diǎn)贊胁塞!如果對(duì)文中的內(nèi)容有任何疑問(wèn),歡迎留言討論。想知道我接下來(lái)會(huì)寫(xiě)些什么啸罢?歡迎訂閱我的掘金專欄或知乎專欄:《前端周刊:讓你在前端領(lǐng)域跟上時(shí)代的腳步》编检。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扰才,隨后出現(xiàn)的幾起案子允懂,更是在濱河造成了極大的恐慌,老刑警劉巖衩匣,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累驮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡舵揭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門躁锡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)午绳,“玉大人,你說(shuō)我怎么就攤上這事映之±狗伲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵杠输,是天一觀的道長(zhǎng)赎败。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蠢甲,這世上最難降的妖魔是什么僵刮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮鹦牛,結(jié)果婚禮上搞糕,老公的妹妹穿的比我還像新娘。我一直安慰自己曼追,他們只是感情好窍仰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著礼殊,像睡著了一般驹吮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晶伦,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天碟狞,我揣著相機(jī)與錄音,去河邊找鬼坝辫。 笑死篷就,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竭业,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼智润,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了未辆?” 一聲冷哼從身側(cè)響起窟绷,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咐柜,沒(méi)想到半個(gè)月后兼蜈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拙友,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年为狸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遗契。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辐棒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牍蜂,到底是詐尸還是另有隱情漾根,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布鲫竞,位于F島的核電站辐怕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏从绘。R本人自食惡果不足惜寄疏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望顶考。 院中可真熱鬧赁还,春花似錦、人聲如沸驹沿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渊季。三九已至朋蔫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間却汉,已是汗流浹背驯妄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留合砂,地道東北人青扔。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親微猖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谈息,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容