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 20 中场休息,来做点酷东西(型别修正跟除点小虫)

>>:  【把玩Azure DevOps】Day23 CI/CD从这里:建立第一个Releases Pipeline

[从0到1] C#小乳牛 练成基础程序逻辑 Day 22 - 泛型集合 List<T>

组合式哑铃 | 用过就回不去惹 | 还不快new一个~ ...

[Day13] Hoisting

提升(Hoisting) 在 JavaScript 里指的是在执行代码之前,直译器(interpre...

Day.21 「物件也有继承问题?」 —— JavaScript 继承 与 原型链

我们每新增一个函式,浏览器都会向函式内新增一个属性叫 prototype function Per...

Day 25 - 介绍 OSPF

今天我们来配 OSPF。 OSPF 是一种 IGP (Interior Gateway Protoc...

Day-29 快速学习Excel时间函式

今日练习档 ԅ( ¯་། ¯ԅ) 今天要来带大家认识两个与时间有关的函式,分别为TODAY()以及N...