[譯] 為什麼我更喜歡物件而不是switch語句

廣州蘆葦科技web前端發表於2019-02-27

原文自工程師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不是?嗯,讓我們來看看:

  1. 它會尋找物件中的鍵。
  2. 它會看到vscode鍵的值是false。
  3. 它會試圖返回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'); 
複製程式碼

現在我們有一種方法:

  1. 更有條理
  2. 更易擴充
  3. 更容易維護
  4. 更容易測試
  5. 更安全並且副作用和風險更小

注意事項

正如預期的那樣,所有的方法都有其缺點,這一個也不例外。

  1. 由於我們正在使用物件,所以我們將佔用記憶體中的一些臨時空間來儲存它們,當定義物件的作用域不再可訪問時,這個空間將被垃圾收集器釋放。
  2. 當沒有太多情況需要處理時,物件方法可能比switch語句的速度要慢,這可能是因為我們正在建立一個資料結構,然後接收一個鍵,然而在switch中,我們只是檢查值並返回值。

結論

本文不打算改變你的編碼風格或讓你停止使用switch語句,它只是試圖提高你對switch語句的認識,以便它可以正確使用,並開放你的思想探索新的替代方案,在這種情況下,我已經分享了我喜歡使用的方法,但還有更多,例如,你可能想看一個稱為模式匹配的ES6提案,如果你不喜歡它,你可以繼續探索。

好的開發未來,就是這樣,我希望你喜歡這篇文章,如果你這樣做,你可能會喜歡這篇關於工廠模式的文章。此外,不要忘記分享和點贊,你可以在twitter上找到我或通過我的電子郵件duranenmanuel@gmail.com聯絡我,下一個見。

閱讀EnmaScript.com上釋出的原始文章

譯者總結

本文介紹了一種使用物件去代替我們之前用switch和繁瑣的if else語句的方法。其實,很多情況下我們可以利用物件與其他組合搭配寫出更為高效或可維護的程式碼。當然,如何去靈活地使用物件去處理一些對應的情況,還是靠我們自己。好的,這篇就總結到這了,不知道對你們有什麼啟發。相信會給到一些幫助給讀者,我們可不是一個只會if else的工程師,哈哈~

作者簡介:張敏,蘆葦科技web前端開發工程師,低調冷幽默,深藏不露。代表作品:微魚娃娃機系統、TopShow活動報名小程式。擅長網站建設、微信公眾號開發、微信小程式開發、小遊戲製作、企業微信製作、H5建設,專注於前端框架、互動設計、影像繪製、資料分析等研究。

歡迎和我們一起並肩作戰: web@talkmoney.cn 訪問 www.talkmoney.cn 瞭解更多

最後,缺物件嗎?趕緊關注我!!給你安排安排!

相關文章