原文自工程師Enmanuel Durán博客,傳送門
最近(或者不是最近,這完全取決于您什么時候閱讀這邊文章),我正在跟我的團隊伙伴討論如何去處理這種需要根據不同的值去處理不同的情況的方法,通常對于這種情況下,人們喜歡使用switch語句或者使用很多if搭配else if條件。在本文中我將重點介紹第三種方式(我更為喜歡的方法),即使用對象進行快速地查找。
switch 語句
switch語句允許我們根據傳遞的表達式的值來執行表達式并執行某些特定的操作,通常當你學習編寫代碼和算法時,你會發現可以將它專門用于多種值的情況,你開始使用它,它看起來很好,你很快意識到它給了你很大的自由,耶!但是要小心,自由度越大責任感也就越大。
讓我們快速了解一下典型的switch語句是怎么樣的:
switch (expression) {case x: {/* Your code here */break;}case y: {/* Your code here */break;}default: {/* Your code here */}
}
很好,現在有一些你可能不知道需要注意的事情:
可選的關鍵字break
break關鍵字允許我們在滿足條件時停止執行塊。如果不將break關鍵字添加到switch語句,則不會拋出錯誤。如果我們不小心忘記break的話,可能意味著在執行代碼的時候你甚至不知道代碼已經正在執行中了,這還會在調試問題時增加實現結果的的不一致性、突變、內存泄漏和復雜度等問題。我們來看看這個問題的一種表示形式:
switch ('first') {case 'first': {console.log('first case');}case 'second': {console.log('second case');}case 'third': {console.log('third case');break;}default: {console.log('infinite');}
}
如果你在控制臺中執行這段代碼,你會看到輸出是
firt case
second case
third case
switch語句在第二種和第三種情況下也會執行,即使第一種情況已經是正確的,然后它在第三種情況塊中找到關鍵字break并停止執行,控制臺中沒有警告或錯誤讓你知道它,這會讓你認為這是預期的行為。
每種情況下的大括號都不是強制的
在javascript中大括號代表著代碼塊,因為自ECMAscript 2015我們可以使用關鍵字聲明塊編譯變量,如const或let(但對于switch來說并不是很好),因為大括號不是強制性的,重復聲明會導致錯誤變量,讓我們看看當我們執行下面的代碼時會發生什么:
switch ('second') {case 'first':let position = 'first';console.log(position);break;case 'second':let position = 'second';console.log(position);break;default:console.log('infinite');
}
我們會得到:
Uncaught SyntaxError: Identifier 'position' has already been declared
這里將會返回一個錯誤,因為變量position已經在第一種情況下聲明過了,并且由于它沒有大括號,所以在第二種情況下嘗試聲明它,它已經存在了。
現在想象使用帶有不一致break關鍵字和大括號的switch語句時會發生什么事:
switch ('first') {case 'first':let position = 'first';console.log(position);case 'second':console.log(`second has access to ${position}`);position = 'second';console.log(position);default:console.log('infinite');
}
控制臺將輸出以下內容:
first
second has access to first
second
infinite
試想一下,由此而引起的錯誤和突變是如此之多,其可能性是無窮無盡的……不管怎樣,switch語句已經講夠了,我們來這里是為了討論一種不同的方法,我們來這里是為了討論對象。
更安全查找的對象
對象查找速度很快,隨著它們的大小增長它們也會更快,它們也允許我們將數據表示為對于條件執行非常有用的鍵值對。
使用字符串
讓我們從簡單的switch示例開始,讓我們假設我們需要有條件地保存和返回一個字符串的情景,并使用我們的對象:
const getPosition = position => {const positions = {first: 'first',second: 'second',third: 'third',default: 'infinite'};return positions[position] || positions.default;
};const position = getPosition('first'); // Returns 'first'
const otherValue = getPosition('fourth'); // Returns 'infinite'
這可以做同樣類型的工作,如果你想進一步的壓縮簡化代碼,我們可以利用箭頭函數:
const getPosition = position =>({first: 'first',second: 'second',third: 'third'}[position] || 'infinite');const positionValue = getPosition('first'); // Returns 'first'
const otherValue = getPosition('fourth'); // Returns 'infinite'
這與前面的實現完全相同,我們在更少的代碼行中實現了更緊湊的解決方案。
現在讓我們更實際一點,不是我們寫的所有條件都會返回簡單的字符串,其中很多會返回布爾值,執行函數等等。
使用布爾值
我喜歡創建返回類型一致的值的函數,但是,由于javascript是動態類型語言,因此可能存在函數可能返回動態類型的情況,因此我將在此示例中考慮這一點,如果找不到鍵,我將創建一個返回布爾值,未定義或字符串的函數。
const isNotOpenSource = language =>({vscode: false,sublimetext: true,neovim: false,fakeEditor: undefined}[language] || 'unknown');const sublimeState = isNotOpenSource('sublimetext'); // Returns true
看起來不錯,對吧?別急,好像我們有一個問題......如果我們調用帶有參數的函數,會發生什么'vscode'或fakeEditor不是?嗯,讓我們來看看:
- 它會尋找對象中的鍵。
- 它會看到vscode鍵的值是false。
- 它會試圖返回false,但因為false || 'unknown'是unknown,我們最終會返回一個不正確的值。
對于key為fakeEditor也會有同樣的問題
Oh no, 好吧,不要驚慌,讓我們來解決這個問題:
const isNotOpenSource = editor => {const editors = {vscode: false,sublimetext: true,neovim: false,fakeEditor: undefined,default: 'unknown'};return editor in editors ? editors[editor] : editors.default;
};const codeState = isNotOpenSource('vscode'); // Returns false
const fakeEditorState = isNotOpenSource('fakeEditor'); // Returns undefined
const sublimeState = isNotOpenSource('sublimetext'); // Returns true
const webstormState = isNotOpenSource('webstorm'); // Returns 'unknown'
這就解決了問題,但是......我希望你們問自己一件事:這真的是問題所在嗎?我認為我們應該更關心為什么我們需要一個返回布爾值,未定義值或字符串的函數,這里存在嚴重的不一致性,無論如何,對于這樣一個非常棘手的情況這也只是一個可能的解決方案。
使用函數
我們繼續講函數,通常我們會發現我們需要根據參數來執行一個函數,假設我們需要根據輸入的類型來解析一些輸入值,如果解析器沒有注冊,我們只返回值:
const getParsedInputValue = type => {const emailParser = email => `email, ${email}`;const passwordParser = password => `password, ${password}`;const birthdateParser = date => `date , ${date}`;const parsers = {email: emailParser,password: passwordParser,birthdate: birthdateParser,default: value => value};return parsers[type] || parsers.default;
};// We select the parser with the type and then passed the dynamic value to parse
const parsedEmail = getParsedInputValue('email')('myemail@gmail.com'); // Returns email, myemail@gmail.com
const parsedName = getParsedInputValue('name')('Enmanuel'); // Returns 'Enmanuel'
如果我們有一個類似的函數返回另一個函數但這次沒有參數,我們可以改進代碼,以便在調用第一個函數時直接返回,如:
const getValue = type => {const email = () => 'myemail@gmail.com';const password = () => '12345';const parsers = {email,password,default: () => 'default'};return (parsers[type] || parsers.default)(); // we immediately invoke the function here
};const emailValue = getValue('email'); // Returns myemail@gmail.com
const passwordValue = getValue('name'); // Returns default
通用代碼塊
Switch語句允許我們為多個條件定義公共代碼塊。
switch (editor) {case 'atom':case 'sublime':case 'vscode':return 'It is a code editor';break;case 'webstorm':case 'pycharm':return 'It is an IDE';break;default:return 'unknown';
}
我們如何使用對象來處理它?我們可以在下一個方面做到這一點:
const getEditorType = type => {const itsCodeEditor = () => 'It is a code editor';const itsIDE = () => 'It is an IDE';const editors = {atom: itsCodeEditor,sublime: itsCodeEditor,vscode: itsCodeEditor,webstorm: itsIDE,pycharm: itsIDE,default: () => 'unknown'};return (editors[type] || editors.default)();
};const vscodeType = getEditorType('vscode');
現在我們有一種方法:
- 更有條理
- 更易拓展
- 更容易維護
- 更容易測試
- 更安全并且副作用和風險更小
注意事項
正如預期的那樣,所有的方法都有其缺點,這一個也不例外。
- 由于我們正在使用對象,所以我們將占用內存中的一些臨時空間來存儲它們,當定義對象的作用域不再可訪問時,這個空間將被垃圾收集器釋放。
- 當沒有太多情況需要處理時,對象方法可能比switch語句的速度要慢,這可能是因為我們正在創建一個數據結構,然后接收一個鍵,然而在switch中,我們只是檢查值并返回值。
結論
本文不打算改變你的編碼風格或讓你停止使用switch語句,它只是試圖提高你對switch語句的認識,以便它可以正確使用,并開放你的思想探索新的替代方案,在這種情況下,我已經分享了我喜歡使用的方法,但還有更多,例如,你可能想看一個稱為模式匹配的ES6提案,如果你不喜歡它,你可以繼續探索。
好的開發未來,就是這樣,我希望你喜歡這篇文章,如果你這樣做,你可能會喜歡這篇關于工廠模式的文章。此外,不要忘記分享和點贊,你可以在twitter上找到我或通過我的電子郵件duranenmanuel@gmail.com聯系我,下一個見。
閱讀EnmaScript.com上發布的原始文章
譯者總結
本文介紹了一種使用對象去代替我們之前用switch和繁瑣的if else語句的方法。其實,很多情況下我們可以利用對象與其他組合搭配寫出更為高效或可維護的代碼。當然,如何去靈活地使用對象去處理一些對應的情況,還是靠我們自己。好的,這篇就總結到這了,不知道對你們有什么啟發。相信會給到一些幫助給讀者,我們可不是一個只會if else的工程師,哈哈~