在JavaScript中優(yōu)雅的進(jìn)行條件判斷

本文是一篇翻譯文章。

原文鏈接: https://dev.to/hellomeghna/tips-to-write-better-conditionals-in-javascript-2189

什么是條件語句

不管是什么編程語言橄霉,代碼中都少不了做判斷誓篱,以及依據(jù)一個輸入的條件決定要執(zhí)行不同的操作姿骏。

例如,在一款游戲中摧玫,如果玩家的生命數(shù)小于0,游戲就宣告結(jié)束稚新。在一款天氣預(yù)報app中,在早上顯示太陽的圖片跪腹,在夜間顯示星星和月亮的圖片褂删。在本文中我們就將介紹如何在JavaScript中進(jìn)行類似的條件處理。

當(dāng)你寫JavaScript時冲茸,有時候會寫出一大堆包含許多條件判斷的代碼屯阀。這些條件語句,在開始的時候可能還比較好理解轴术,但是過一陣子之后就變得一團(tuán)糟了蹲盘。其實有比if/else更棒的方式去實現(xiàn)條件判斷。

這里就有一些關(guān)于如何寫出干凈優(yōu)雅的條件判斷的建議膳音。

目錄

  1. Array.includes
  2. 盡早退出和返回
  3. 用Object遍歷 或者 Map 取代 Switch 表達(dá)式
  4. 使用默認(rèn)參數(shù) 和 解構(gòu)賦值
  5. 使用Array.every 和 Array.some去實現(xiàn)所有 和 部分條件判斷
  6. Use Optional Chaining and Nullish Coalescing

1.Array.includes

如果有多個條件可以使用Array.includes

例如:

function printAnimals(animal) {
    if (animal === 'dog' || animal === 'cat') {
        console.log(`I have a ${animal}`);
    }
}

console.log(printAnimals('dog')); // I have a dog

上面的代碼似乎看起來還行,那是因為我們只需要檢測兩種小動物铃诬。然而我們并不確定用戶會輸入什么祭陷。如果動物的類型變多了呢苍凛?如果我們繼續(xù)通過擴(kuò)展|| 條件判斷來滿足需求,我們的代碼會變得越來越難維護(hù)兵志,并且看起來亂亂的醇蝴。

解決方案:

我們可以用 Array.includes來重構(gòu)一下上面的代碼 :

function printAnimals(animal) {
    const animals = ['dog', 'cat', 'hamster', 'turtle'];

    if (animals.includes(animal)) {
        console.log(`I have a ${animal}`);
    }
}

console.log(printAnimals('hamster')); // I have a hamster

這里我們創(chuàng)建了一個數(shù)組存放動物,這樣判斷條件就可以和剩余的代碼隔離開了∠牒保現(xiàn)在悠栓,如果我們想繼續(xù)擴(kuò)充條件,我們只需要往數(shù)組里添加新的元素就可以了按价。(清晰多了呢)

2.盡早退出和返回

這是一個非巢咽剩酷的小技巧,可以使你的代碼看起來簡潔楼镐。我記得我從工作的第一天起癞志,我就被教導(dǎo),在條件判斷時要early exit(盡早退出)框产。

讓我們?yōu)樯弦粋€示例多添加些條件凄杯。如果animal 不再是一個簡單的string了,而是一個有特定屬性的object秉宿。

所以現(xiàn)在需求變成了下面這樣:

  • 如果沒有animal戒突,拋出一個錯誤
  • 打印出animal的類型
  • 打印出animal的名字
  • 打印出animal的類型
const printAnimalDetails = animal => {
    let result; // declare a variable to store the final value

    // condition 1: check if animal has a value
    if (animal) {

        // condition 2: check if animal has a type property
        if (animal.type) {

            // condition 3: check if animal has a name property
            if (animal.name) {

                // condition 4: check if animal has a gender property
                if (animal.gender) {
                    result = `${animal.name} is a ${animal.gender} ${animal.type};`;
                } else {
                    result = "No animal gender";
                }
            } else {
                result = "No animal name";
            }
        } else {
            result = "No animal type";
        }
    } else {
        result = "No animal";
    }

    return result;
};

console.log(printAnimalDetails()); // 'No animal'

console.log(printAnimalDetails({type: "dog", gender: "female"})); // 'No animal name'

console.log(printAnimalDetails({type: "dog", name: "Lucy"})); // 'No animal gender'

console.log(
    printAnimalDetails({type: "dog", name: "Lucy", gender: "female"})
); // 'Lucy is a female dog'

對于上面的代碼,你怎么看呢描睦?

上面的代碼沒什么bug膊存,但是看起來太長了,而且很難維護(hù)呢酌摇。一個新人可能得花個一上午來找哪些括號是一對的呢(手動滑稽)膝舅。如果邏輯再復(fù)雜點呢,if/else就更多了窑多。我們可以用?:仍稀,$$運(yùn)算符等來重構(gòu)上面的代碼。但是埂息,我就不(哈哈哈……)技潘。我使用了多次return來重構(gòu)了上面的代碼。

const printAnimalDetails = ({type, name, gender } = {}) => {
    if(!type) return 'No animal type';
    if(!name) return 'No animal name';
    if(!gender) return 'No animal gender';

// Now in this line of code, we're sure that we have an animal with all //the three properties here.

    return `${name} is a ${gender} ${type}`;
}

console.log(printAnimalDetails()); // 'No animal type'

console.log(printAnimalDetails({ type: dog })); // 'No animal name'

console.log(printAnimalDetails({ type: dog, gender: female })); // 'No animal name'

console.log(printAnimalDetails({ type: dog, name: 'Lucy', gender: 'female' })); // 'Lucy is a female dog'

在重構(gòu)版中千康,也用到了對象的解構(gòu)賦值和函數(shù)參數(shù)的默認(rèn)值享幽。默認(rèn)值的作用是,如果我們沒有傳參(undifined)拾弃,也能保證不會報錯值桩。

另一個例子:

function printVegetablesWithQuantity(vegetable, quantity) {
    const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];

    // condition 1: vegetable should be present
    if (vegetable) {
        // condition 2: must be one of the item from the list
        if (vegetables.includes(vegetable)) {
            console.log(`I like ${vegetable}`);

            // condition 3: must be large quantity
            if (quantity >= 10) {
                console.log('I have bought a large quantity');
            }
        }
    } else {
        throw new Error('No vegetable from the list!');
    }
}

printVegetablesWithQuantity(null); //  No vegetable from the list!
printVegetablesWithQuantity('cabbage'); // I like cabbage
printVegetablesWithQuantity('cabbage', 20);
// 'I like cabbage`
// 'I have bought a large quantity'

現(xiàn)在,上面的例子中包含:

  • 1對if/else用來過濾不可用的條件
  • 3級嵌套if

接下來我要介紹我們的套路了—— 當(dāng)遇到不可用的條件時盡早退出函數(shù)

function printVegetablesWithQuantity(vegetable, quantity) {

    const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];

    // condition 1: throw error early
    if (!vegetable) throw new Error('No vegetable from the list!');

    // condition 2: must be in the list
    if (vegetables.includes(vegetable)) {
        console.log(`I like ${vegetable}`);

        // condition 3: must be a large quantity
        if (quantity >= 10) {
            console.log('I have bought a large quantity');
        }
    }
}

這樣重構(gòu)后豪椿,我們就少了一層if嵌套奔坟,當(dāng)你的條件判斷比較長時携栋,這種代碼風(fēng)格尤為好用。

我們能進(jìn)一步減少if嵌套咳秉,通過對條件進(jìn)行取反婉支,然后return。下面就是具體實現(xiàn):

function printVegetablesWithQuantity(vegetable, quantity) {

    const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];

    if (!vegetable) throw new Error('No vegetable from the list!');
    // condition 1: throw error early

    if (!vegetables.includes(vegetable)) return;
    // condition 2: return from the function is the vegetable is not in 
    //  the list 


    console.log(`I like ${vegetable}`);

    // condition 3: must be a large quantity
    if (quantity >= 10) {
        console.log('I have bought a large quantity');
    }
}

通過對第二個條件取反澜建,代碼里再也看不到if的嵌套了向挖。這種技巧適用于當(dāng)我們有好多條件判斷,并且當(dāng)滿足某一個時炕舵,不再進(jìn)行剩余的邏輯處理何之。

因此,我們的目標(biāo)是消滅嵌套幕侠,及早return帝美。但是return大法好,也不能"貪杯"啊~

3. 用Object遍歷 或者 Map 取代 Switch 表達(dá)式

讓我們看下這個例子晤硕,我們想基于顏色打印出水果:

function printFruits(color) {
    // use switch case to find fruits by color
    switch (color) {
        case 'red':
            return ['apple', 'strawberry'];
        case 'yellow':
            return ['banana', 'pineapple'];
        case 'purple':
            return ['grape', 'plum'];
        default:
            return [];
    }
}

printFruits(null); // []
printFruits('yellow'); // ['banana', 'pineapple']

上面的代碼沒什么錯誤悼潭,就是看起來有點長。我們可以用Object來實現(xiàn)同樣的效果:

// use object literal to find fruits by color
const fruitColor = {
    red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum']
};

function printFruits(color) {
    return fruitColor[color] || [];
}

當(dāng)然也可以用Map

// use Map to find fruits by color
const fruitColor = new Map()
    .set('red', ['apple', 'strawberry'])
    .set('yellow', ['banana', 'pineapple'])
    .set('purple', ['grape', 'plum']);

function printFruits(color) {
    return fruitColor.get(color) || [];
}

Map是ES2015 (Es6)的語法舞箍,大家注意兼容性呀舰褪!

也能用Array.filter來實現(xiàn):

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'strawberry', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'pineapple', color: 'yellow' },
    { name: 'grape', color: 'purple' },
    { name: 'plum', color: 'purple' }
];

function printFruits(color) {
    return fruits.filter(fruit => fruit.color === color);
}

4.使用默認(rèn)參數(shù) 和 解構(gòu)賦值

我們寫JavaScript時,經(jīng)常需要去檢查null/undefined疏橄,并對參數(shù)賦默認(rèn)值占拍,否則就會報錯。

function printVegetablesWithQuantity(vegetable, quantity = 1) {
// if quantity has no value, assign 1

    if (!vegetable) return;
    console.log(`We have ${quantity} ${vegetable}!`);
}

//results
printVegetablesWithQuantity('cabbage'); // We have 1 cabbage!
printVegetablesWithQuantity('potato', 2); // We have 2 potato!

如果vegetable是一個對象呢捎迫?我們能給它默認(rèn)賦值么晃酒?

function printVegetableName(vegetable) {
    if (vegetable && vegetable.name) {
        console.log (vegetable.name);
    } else {
        console.log('unknown');
    }
}

printVegetableName(undefined); // unknown
printVegetableName({}); // unknown
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage

上面的例子中,如果vegetable有可用的值窄绒,我們就打印出它的name贝次,否則打印unknow

我們可以用默認(rèn)值 和 解構(gòu)賦值代替if (vegetable && vegetable.name) {}彰导。

// destructing - get name property only
// assign default empty object {}

function printVegetableName({name} = {}) {
    console.log (name || 'unknown');
}


printVegetableName(undefined); // unknown
printVegetableName({ }); // unknown
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage

因為我們只需要name屬性蛔翅,我們可以{name}將它解構(gòu)出來,然后我們就可以使用name變量了位谋,這樣就不需要使用vegetable.name了山析。

我們也給函數(shù)的參數(shù)設(shè)置了一個默認(rèn)值{},否則當(dāng)我們執(zhí)行printVegeTable(undefined)的時就會報錯Cannot destructure property name of undefined or null掏父,因為undefined不是對象笋轨,是不能解構(gòu)的。

5.使用Array.every 和 Array.some去實現(xiàn)所有 和 部分條件判斷

我么可以使用數(shù)組的這些方法來減少代碼行數(shù)。下面的代碼中我們想判斷是否所有的水果都是紅色翩腐。

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
];

function test() {
    let isAllRed = true;

    // condition: all fruits must be red
    for (let f of fruits) {
        if (!isAllRed) break;
        isAllRed = (f.color == 'red');
    }

    console.log(isAllRed); // false
}

代碼太長了鸟款。我們可以換Array.every試試:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
];

function test() {
    // condition: short way, all fruits must be red
    const isAllRed = fruits.every(f => f.color == 'red');

    console.log(isAllRed); // false
}

同理,如果我們想判斷部分水果是紅色的茂卦,我們可以用Array.some來實現(xiàn)。

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
];

function test() {
    // condition: if any fruit is red
    const isAnyRed = fruits.some(f => f.color == 'red');

    console.log(isAnyRed); // true
}

6. Use Optional Chaining and Nullish Coalescing

這兩個功能在JavaScript中是非常有用的组哩。但是目前支持力度還不是很好等龙,所以需要使用babel進(jìn)行編譯。

Optional chaining使我們可以跳過中間層級去檢查一個樹狀解構(gòu)是否包含某個屬性伶贰。nullish coalescing可以和

Optional chaining配合使用蛛砰,來為變量賦默認(rèn)值。

下面是個例子:

const car = {
    model: 'Fiesta',
    manufacturer: {
        name: 'Ford',
        address: {
            street: 'Some Street Name',
            number: '5555',
            state: 'USA'
        }
    }
}

// to get the car model
const model = car && car.model || 'default model';

// to get the manufacturer street
const street = car && car.manufacturer && car.manufacturer.address &&
    car.manufacturer.address.street || 'default street';

// request an un-existing property
const phoneNumber = car && car.manufacturer && car.manufacturer.address
    && car.manufacturer.phoneNumber;

console.log(model) // 'Fiesta'
console.log(street) // 'Some Street Name'
console.log(phoneNumber) // undefined

所以如果我們想一輛汽車的制造商是否是美國黍衙,我們必須這么寫代碼:

const isManufacturerFromUSA = () => {
    if(car && car.manufacturer && car.manufacturer.address &&
        car.manufacturer.address.state === 'USA') {
        console.log('true');
    }
}


checkCarManufacturerState() // 'true'

你能看到泥畅,這么寫代碼是多么的凌亂。早已經(jīng)有一些第三方庫琅翻,像lodash或者idx有自己的函數(shù)位仁,去簡化這個操作。例如lodash_.get方椎。然而聂抢,如果JavaScript能原生支持這種操作就更好了。

下面就是一個例子:

// to get the car model
const model = car?.model ?? 'default model';

// to get the manufacturer street
const street = car?.manufacturer?.address?.street ?? 'default street';

// to check if the car manufacturer is from the USA
const isManufacturerFromUSA = () => {
    if(car?.manufacturer?.address?.state === 'USA') {
        console.log('true');
    }
}

這個代碼看起來漂亮多了棠众,而且易于維護(hù)琳疏。這個特性已經(jīng)在 TC39 stage 3提案中了。我們再等等就可以用上了闸拿。

總結(jié)

讓我們試著使用這些建議來寫一些干凈易于維護(hù)的代碼吧空盼,因為那些冗長的條件判斷,再過幾個月之后連你自己都看不懂了新荤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揽趾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子迟隅,更是在濱河造成了極大的恐慌但骨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件智袭,死亡現(xiàn)場離奇詭異奔缠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吼野,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門校哎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事闷哆⊙埽” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵抱怔,是天一觀的道長劣坊。 經(jīng)常有香客問我,道長屈留,這世上最難降的妖魔是什么局冰? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮灌危,結(jié)果婚禮上康二,老公的妹妹穿的比我還像新娘。我一直安慰自己勇蝙,他們只是感情好沫勿,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著味混,像睡著了一般产雹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惜傲,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天洽故,我揣著相機(jī)與錄音,去河邊找鬼盗誊。 笑死时甚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哈踱。 我是一名探鬼主播荒适,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼开镣!你這毒婦竟也來了刀诬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤邪财,失蹤者是張志新(化名)和其女友劉穎陕壹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體树埠,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糠馆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怎憋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片又碌。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡九昧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出毕匀,到底是詐尸還是另有隱情铸鹰,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布皂岔,位于F島的核電站蹋笼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凤薛。R本人自食惡果不足惜姓建,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缤苫。 院中可真熱鬧,春花似錦墅拭、人聲如沸活玲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舒憾。三九已至,卻和暖如春穗熬,著一層夾襖步出監(jiān)牢的瞬間镀迂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工唤蔗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留探遵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓妓柜,卻偏偏與公主長得像箱季,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子棍掐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355