共 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í)代的腳步》编检。