無論你在什麼時候讀程式碼,您都必須注意到匿名函式。有時它們被稱為 lambda,有時是匿名函式,不管怎樣,我認為他們是不好使用的。
如果你不知道匿名函式是什麼,這裡有一個引語:
匿名函式是一種在執行時動態宣告的函式。它們之所以被稱為匿名函式是因為不同於普通函式,它們並沒有函式名。 — Helen Emerson, Helephant.com
匿名函式形式如下:
1 2 3 |
function () { ... code ... } OR (args) => { ... code .. } |
今天我嘗試讓大家理解只有在絕對需要的情況下才使用匿名函式的想法。匿名函式不應該是首選,而且你自己也應該知道為什麼使用它。當理解這種想法之後,你的程式碼會變得更簡潔,更容易維護,並且更容易跟蹤bug。
先從避免使用匿名函式的三個理由開始:
無論你多麼擅長寫程式碼,出現錯誤也是不可避免的。有時候,這些錯誤很容易被查出,有時候並不容易。
如果你知道這些錯誤來自哪裡,那麼錯誤會很容易被查出來。為了容易查出錯誤,我們使用這個被叫做堆疊軌跡的工具。如果你不瞭解堆疊軌跡,goole給出了很棒的介紹。
假設現在有一個非常簡單的專案:
1 2 3 4 5 6 7 |
function start () { (function middle () { (function end () { console.lg('test'); })() })() } |
上面程式碼裡面有一個非常愚蠢的錯誤,拼寫錯誤(console.log)。在小專案裡面,這個拼寫錯誤不是什麼大問題。如果這是一個有非常多模組非常大的專案一小段,問題就大了。假設這個愚蠢的錯誤不是你犯的,那麼新來的初級工程師將會在他休假之前把這個錯誤提交到程式碼庫!
現在,我們必須追查。 使用我們精心命名的函式,我們得到如下的堆疊跟蹤:
謝謝你命名你的函式,初級開發者們! 現在我們可以輕鬆地追蹤到這個bug。
但是..一旦我們解決了這個問題,就會發現還有另一個bug。 這次是一位更資深的開發人員介紹的。這個人知道lambdas(匿名函式),並在程式碼中大量使用它們。 結果他們偶然發現了一個bug,我們的工作就是追蹤它。
下面是程式碼:
1 2 3 4 5 6 7 |
(function () { (function () { (function () { console.lg('test'); })(); })(); })(); |
吃不吃驚,這名開發者也忘記了如何拼寫console.log了!這也太巧合了吧!令人感到遺憾的是,他們都沒有命名他們的函式。
那麼控制檯會輸出什麼呢?
好吧,我們至少還有行號,對吧?在這個例子中,看起來我們有大約7行程式碼。如果我們處理一大段程式碼會如何呢?比如一萬行程式碼?行號的跨度如此之大該怎麼辦呢?如果程式碼被摺疊後有沒有一個程式碼地圖檔案,那麼對行號的渲染是不是根本就是沒有什麼用了呢?
我想對這些問題的回答相當簡單,答案就是:想這些會讓你一整天都會過的相當糟心。
可讀性
咦,我聽說你還不信。你仍舊對你的匿名函式戀戀不捨,並且還從未發生過bug。我的錯,你的程式碼是完整的。但是讓我們看看這個!
看看下面兩段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
function initiate (arguments) { return new Promise((resolve, reject) => { try { if (arguments) { return resolve(true); } return resolve(false); } catch (e) { reject(e); } }); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
initiate(true) .then(res => { if (res) { doSomethingElse(); } else { doSomething(); } ).catch(e => { logError(e.message); restartApp(); } ); |
這是一個非常不正常的例子,但是我相信你已經明白我要說什麼了。我們反悔了一個promise方法,我們用這個promise物件/方法處理不同的響應。
你也許會認為幾段程式碼讀起來並不難,但我認為它們可以變得更好!
如果我們去掉所有的匿名函式會怎樣呢?
1 2 3 |
function initiate (arguments) { return new Promise(checkForArguments); } |
1 2 3 4 5 6 7 8 9 10 |
function checkForArguments (resolve, reject) { try { if (arguments) { return resolve(true); } return resolve(false); } catch (e) { reject(e); } } |
1 2 3 4 5 6 7 |
function evaluateRes (res) { if (res) { doSomethingElse(); } else { doSomething(); } } |
1 2 3 4 |
function handleError (e) { logError(e.message); restartApp(); } |
1 2 3 |
initiate(true) .then(evaluateRes) .catch(handleError); |
好,先講清楚:這部分程式碼更長,但我不認為只是增加了可讀性!我們精心命名的函式與匿名函式不一樣,只要我們一看到它們的名字就知道它們的功能是什麼。這避免了在評估程式碼時出現心理障礙。
這也有助於分清楚其中的關係。與建立一個方法、將其傳遞、然後執行邏輯不同,在第二個例子中的引數被給到了then,catch只是指向了發生所有事情的函式。
關於更具有可讀性,我沒有什麼再能說服你的了。但是也許你還沒被說服的話,我可以試一下最後的論據。
可重用性
你注意到上一個例子了嗎?上個例子中的函式的使用範圍從引數和初始化函式,變為讓所有函式都能使用。
當你使用匿名函式時這些函式很難在你的應用程式內重複使用。
可重用性將不復存在,最終你會一遍又一遍地寫重複的程式碼。正如我們所見的,程式碼寫的越少引入的Bug就越少,使用者必須載入的內容就越少。所有人都會因此獲益!
相反的,命名函式可以全域性使用,而不需要像變數一樣到處傳遞。你的程式碼的可重用性會更好,
匿名函式有可取的地方嗎?
有。雖然很不願意承認,但有時候使用匿名函式是最好的選擇。
1 2 3 4 5 6 |
const stuff = [ { hide: true, name: 'justin' }, { hide: false, name: 'lauren' }, { hide: false, name: 'max' }, ]; const filteredStuff = stuff.filter(s => !s.hide); |
上邊程式碼中的匿名函式s => !s.hide非常簡單,即使不能在別的地方使用也不會對別人有任何影響,而且也可以在stuff.filter中顯示出堆疊呼叫。如果想要重用這段程式碼,最好重用整段程式碼:
1 2 3 |
function filterByHide (array) { return array.filter(item => !item.hide); } |
有時你想把你所有的程式碼封裝到匿名函式中,以保證全域性範圍不會被汙染。
1 2 3 |
(() => { ... your code here ... })(); |
在棧空間中擁有一個頂級的匿名函式真得不會有什麼錯誤。沒有程式碼重用是痛苦的,因為完整的目的是保持方法內含。
我確定這會有其他好的用法,請在評論中自由分享之!
感謝閱讀,現在跳出這些,並停止編寫匿名函式!