iT邦幫忙

2021 iThome 鐵人賽

DAY 20
2
Modern Web

Javascript 從寫對到寫好系列 第 20

Day 20 - OR、AND 的活用方式與短路取值

前言

今天來介紹我個人很常用的小撇步,關於 OR (||) 與 AND (&&),除了很單純用來判斷回傳布林值,同時也可以拿來賦值哦!

但同時,這種方式也很容易因為意想不到的 truthy、falsy value,造成不如預期的結果,需要特別小心。

今天也會講到與其息息相關的 Short Circuit Evaluation,可以大幅縮短判斷式的長度,提高可讀性。

今天就來看看,如何應用它們,以及實戰容易踩到的地雷。

Short Circuit Evaluation

大概可以直翻成「短路取值」,好像應該先來了解一下「短路」: 

短路(Short Circuit)

image alt

最上面是電池,S 是開關,L 是燈泡,S 沒有連通的情況下,電流只能夠走 L;但當 S 接通了,電流可以選擇走 S 或 L,肯定會選電阻比較小的 S,就會造成短路。

沒錯,物理課開始囉!想不到物理知識也可以應用在寫程式呢!

說到短路,大部分人抱持負面印象,因為短路可能會造成火災,或者造成電器損壞,甚至還有「腦袋短路」這種很有趣的說法。

但事實上,如果今天站在一個電子的立場來看,短路才是最正常的啊!因為短路其實就像字面上的意思,要走路的當然挑「短」的路,沒事讓自己這麼累幹嘛?但短的路不一定輕鬆,所以更正確來說,是要找「電阻最小」的路。

那如果套用到寫程式,要怎麼找到「阻礙最小」、「最輕鬆」的路呢?

那當然是讓「運算愈少愈好」!

OR 的短路概念

OR 運算子 (||) 在一般的理解中,就是當「」的概念在使用:

const hasVIPcard = false;
const money = 1500;
if (money > 1000 || hasVIPcard) {
    console.log('尊榮會員');
}

比如上面這個例子,如果 money 大於 1000 或 hasVIPcardtrue任何一個成立都可以是尊榮會員。

但是對於程式來說,既然「只要任何一個成立都可以」,那我當然走短路啊!

由左至右,任何一個判斷式為 true,就回傳 true,後面就不執行了。

因此,上面的程式其實完全沒有去讀 hasVIPcard 的值,就已經進入第 4 行跑 console.log 了。

怎麼知道的呢?我們稍微改一下這個範例:

const isUserHasVipCard = () => {
    console.log('是否有 VIP card?');
    return false;
};
const money = 1500;
if (money > 1000 || isUserHasVipCard()) {
    console.log('尊榮會員');
}

執行結果

尊榮會員

可以看到 isUserHasVipCard 這個函式完全沒有進去,因為程式走「短路」,第一個判斷式就知道答案了,何必再去執行第二個?

OR 短路取值

應用上述的原理,其實 OR 運算子不只能夠用在判斷式,也能夠用在賦值(取值):

const fruit = 'orange' || 'apple';
const fruit2 = '' || 'apple';

console.log(fruit);
console.log(fruit2);

執行結果

orange
apple

很神奇吧!我們平常使用 || 都是拿來「判斷」,都是回傳 boolean,結果這邊居然可以回傳字串!

原理其實跟上面的短路概念是類似的,挑最輕鬆的路走:

OR 語法整理

const result = a || b

如果 a 是 truthy value,就直接回傳 a,若否則回傳 b。相當於:

let result;
if (a) {
    result = a;
} else {
    result = b;
}

const result = a || b || c

如果 a 是 truthy value,就直接回傳 a,若否則看 b 是否為 truthy value,若否則回傳 c。相當於:

let result;
if (a) {
    result = a;
} else if (b) {
    result = b;
} else {
    result = c;
}

想像你是個想要早點打卡下班的電腦程式,當然能少走一個 if 是一個啊!

OR 用來給預設值/初始值

有了上述的基礎,我們對於「||」有了全新的認識,透過 OR 的短路取值,經常被用在當作 default 值:

const arr = [
    { name: 'washing machine', price: 8000 },
    { name: 'TV' },
    { name: 'laptop', price: 25000 },
];

arr.reduce((sum, item) => {
    const price = item.price || 0;
    return sum + price;
}, 0);

執行結果

33000

第 8 行如果少了那個關鍵的 || 0,跑出來的結果會直接變 NaN 哦!

OR 用來階層篩選

如果今天網頁上有個重點廣告區:

  1. 要優先放上 VIP 商品
  2. 如果沒有 VIP 商品,就改放萬元以上的商品
  3. 若真的都沒有,就拿第一個商品來放
const arr = [
    { name: 'washing machine', price: 8000, vip: false },
    { name: 'TV', price: 13500, vip: false },
    { name: 'laptop', price: 25000, vip: false },
];

const advertisement = 
    arr.filter(item => item.vip)[0] ||
    arr.filter(item => item.price > 10000)[0] ||
    arr[0];

console.log(advertisement.name);

執行結果

TV

注意 falsy value

以上是關於 OR 短路取值的應用,但使用這招時(尤其是當預設值時),需要特別注意這種「長得像 falsy 的 truthy」:

const fruitList = [] || ['apple', 'orange', 'banana'];

執行結果

[]

And 的短路概念

And 運算子 (&&) 在一般的理解中,就是當「而且」的概念在使用:

const hasVIPcard = true;
const money = 500;
if (money > 1000 && hasVIPcard) {
    console.log('尊榮又有錢的會員');
}

比如上面這個例子,如果 money 大於 1000 而且 hasVIPcardtrue兩個都要成立才會是尊榮又有錢的會員。

但是對於程式來說,既然「只要任何一個不成立就不是」,那我當然走短路啊!

由左至右,任何一個判斷式為 false,就回傳 false,後面就不執行了。

因此,上面的程式其實完全沒有去讀 hasVIPcard 的值,就已經進入第 4 行跑 console.log 了。

怎麼知道的呢?我們稍微改一下這個範例:

const isUserHasVipCard = () => {
    console.log('是否有 VIP card?');
    return true;
};
const money = 500;
if (money > 1000 && isUserHasVipCard()) {
    console.log('尊榮又有錢的會員');
}

執行結果


可以看到 isUserHasVipCard 這個函式完全沒有進去,因為程式走「短路」,第一個判斷式就知道答案了,何必再去執行第二個?

And 短路取值

應用上述的原理,其實 And 運算子不只能夠用在判斷式,也能夠用在賦值(取值):

const fruit = 'orange' && 'apple';
const fruit2 = '' && 'apple';

console.log(fruit);
console.log(fruit2);

執行結果

apple

很神奇吧!我們平常使用 && 都是拿來「判斷」,都是回傳 boolean,結果這邊居然可以回傳字串!

原理其實跟上面的短路概念是類似的,挑最輕鬆的路走:

AND 語法整理

const result = a && b

如果 a 是 falsy value,就直接回傳 a,若否則回傳 b。相當於:

let result;
if (!a) {
    result = a;
} else {
    result = b;
}

const result = a && b && c

如果 a 是 falsy value,就直接回傳 a,若否則看 b 是否為 falsy value,若否則回傳 c。相當於:

let result;
if (!a) {
    result = a;
} else if (!b) {
    result = b;
} else {
    result = c;
}

比起上面 OR 的版本,就只是多了一個驚嘆號,將 boolean 反轉而已!

AND 用來確認 property path

有了上述的基礎,我們對於「&&」有了全新的認識,透過 AND 的短路取值,可以用來快速判斷一個有很多層的(nested) object,取得指定的深層 property。

比如以下範例會出錯:

const personList = [
    {
        height: 173,
        weight: 63,
        car: {
            color: 'white',
            price: 500000
        }
    }, 
    {
        height: 163,
        weight: 55
    }
];

personList.forEach(person => {
    const carPrice = person.car.price;
    console.log(carPrice);
});

執行結果

500000
Uncaught TypeError: Cannot read property 'price' of undefined

主要是因為有些 person 是沒有 car 的,因此如果硬是取 price 有可能會出錯。

可以改成用 && 來協助取值:

const personList = [
    {
        height: 173,
        weight: 63,
        car: {
            color: 'white',
            price: 500000
        }
    }, 
    {
        height: 163,
        weight: 55
    }
];

personList.forEach(person => {
    const carPrice = person && person.car && person.car.price;
    if (carPrice) {
        console.log(carPrice);
    }
});

執行結果

500000

這樣寫,會在判斷到 person.car 這個 falsy value 時,直接回傳 undefined,就不會繼續找 person.car.price 了。

當然這樣寫起來是非常不美觀的,如果只有一兩層還可以這樣寫,若真的要很多層,也可以考慮使用第三方套件(如:lodash/get)來代替。

結語

今天介紹了大家原本應該很熟的 OR 與 AND,原來它們不只可以透過短路取值來提升效率,甚至還可以用這個方式,做到原本需要一堆 if else 才做得到的事情!

當然這中間是需要相當細心的,因為短路取值的判斷根本,是用昨天討論到的 truthy/falsy value,所以如果對於這兩個概念不熟悉,很容易就會在短路取值上撞牆,務必好好學習這兩天的內容,未來寫的程式可以簡化許多唷!

在真與假之中
拼湊未來的模樣

參考資料

OR MDN
AND MDN


上一篇
Day 19 - 相等判斷與型別轉換
下一篇
Day 21 - Code Review
系列文
Javascript 從寫對到寫好30

1 則留言

1
TD
iT邦新手 4 級 ‧ 2021-10-11 23:02:45

確認 property path 取值的時候也可以考慮使用 Optional chaining

ycchiuuuu iT邦新手 5 級 ‧ 2021-10-12 12:21:37 檢舉
const customerCity = customer.details?.address?.city;

這超方便!我之前專案還沒有支援這個,常常需要靠 lodash 先做確認,算滿麻煩的,好險 JavaScript 一直在進步!

我要留言

立即登入留言