你可能已經知道 ||(OR)和 &&(AND)是邏輯運算子,用來處理布林值的判斷。
但你知道嗎?在 JavaScript 裡,這兩個運算子其實不只是回傳 true 或 false,它們還會根據一套特殊規則,決定要回傳左邊還是右邊的值。
這篇文章會帶你搞懂這套規則,並且學會一個叫做「短路評估」的重要特性。
|| 運算子:找到第一個「有東西的」值
|| 是邏輯上的「或」,最基本的用法是這樣的:只要左右兩邊有任何一邊是 true,結果就是 true。
console.log(true || false);
// → true
console.log(false || false);
// → false這是它在布林值之間的標準行為,應該蠻好理解的。
但在 JavaScript 裡,|| 其實不只能處理布林值,它也可以接受其他型態的值,像是字串、數字、null 等等。
而且,它回傳的結果也不一定是 true 或 false。
事實上,|| 和 && 在 JavaScript 底層的設計中,從來就不是為了「比較出一個布林值」而存在的。
它們做的事情是:看看左右兩邊的值,決定要回傳哪一邊,然後把選中的值原封不動地丟回來。
所以當兩邊都是布林值,回傳的自然就是布林值。
但如果兩邊放的是字串、數字、null 這些東西,它一樣會選一邊回傳,而回傳的就會是那個字串、數字或 null。
那 || 碰到非布林值時,到底會怎麼做?直接來看例子比較清楚:
console.log(null || "user");
// → "user"null 是空的,沒有東西,所以 || 跳過它,回傳右邊的 "user"。
console.log("Agnes" || "user");
// → "Agnes""Agnes" 是一個有內容的字串,|| 覺得它是有效的值,就直接回傳它,右邊的 "user" 根本不會被用到。
看出規律了嗎?|| 會看左邊的值是不是「有東西」,如果有,就回傳左邊;如果左邊是空的、沒意義的值,就回傳右邊。
哪些值算是「空的、沒意義的」?
在 JavaScript 裡,以下這些值會被當成「空的」(通常稱為 falsy 值):
0NaN""(空字串)nullundefined
除了上面這些之外,其他所有值都算是「有東西的」(稱為 truthy 值)。
來看看實際的效果:
console.log(0 || -1);
// → -10 是 falsy,|| 覺得它是空的,所以回傳右邊的 -1。
console.log("" || "!?");
// → "!?"空字串 "" 也是 falsy,所以回傳右邊的 "!?"。
那如果兩邊都是「空的」呢?
console.log(null || "");
// → ""null 是 falsy,所以 || 跳過左邊,回傳右邊的 ""。
即使右邊也是 falsy,|| 還是會把它回傳,因為它已經沒得選了。
用 || 設定預設值
理解了 || 的運作方式之後,我們可以拿它來做一件很實用的事:設定預設值。
假設你在寫一個程式,使用者可能有輸入名字,也可能沒有輸入。
如果沒有輸入,你希望顯示「訪客」作為替代,這時候就可以用 ||:
console.log(null || "訪客");
// → "訪客"左邊是 null(falsy),所以 || 回傳右邊的 "訪客"。
但如果使用者有輸入名字的話:
console.log("小明" || "訪客");
// → "小明""小明" 是 truthy,所以 || 直接回傳左邊的值,右邊的 "訪客" 不會被用到。
這就是所謂的「預設值」技巧:把可能是空的東西放在 || 左邊,把替代值放在右邊。
這個技巧在實際開發中非常常見,可以讓你的程式碼更簡潔。
不過這裡有一個要小心的地方。
|| 沒辦法區分「真的沒有值」和「值剛好是 0 或空字串」。
先看正常的情況,使用者沒有輸入,左邊是 null:
console.log(null || 10);
// → 10沒問題,null 是 falsy,|| 回傳右邊的預設值 10。
但如果使用者剛好輸入了 0:
console.log(0 || 10);
// → 100 也是 falsy,所以被跳過了,回傳了 10。
使用者明明有輸入 0,而且 0 是一個有效的數字,但因為在 JavaScript 裡 0 是 falsy,所以被 || 誤判成「沒有輸入」而跳過了。
這就是用 || 設定預設值的陷阱。
&& 運算子:找到第一個「空的」值
理解了 || 之後,&& 就簡單了,因為它的邏輯剛好相反。
|| 是從左往右找第一個「有東西的」值,而 && 是從左往右找第一個「空的」值。
只要左邊碰到一個 falsy 的值,&& 就覺得「走不下去了」,立刻停下來,把這個擋住它的值回傳給你。
如果左邊是 truthy 的,它就繼續往右走,回傳右邊的值。
來看個例子:
console.log(null && "hello");
// → nullnull 是 falsy,所以 && 直接回傳左邊的 null,右邊的 "hello" 完全不會被處理。
console.log("hi" && "hello");
// → "hello""hi" 是 truthy,所以 && 回傳右邊的 "hello"。
在實際開發中,&& 有一個很常見的用法:安全檢查。
意思是先用 && 確認左邊不是空的,才去執行右邊的操作,避免程式因為碰到空值而當掉。
這個用法要搭配後面會學到的變數和物件才比較好理解,到時候你會很常看到它。
短路評估:右邊的程式碼根本不會執行
前面我們一直在講 || 和 && 會「回傳哪一邊的值」。
但其實還有一件事值得特別拿出來說:當左邊就能決定結果時,右邊的程式碼根本不會被執行。
不是「執行了但不回傳」,而是「完全不會去碰它」。
這有什麼差別?先回憶一下前面講的規則:
|| 是找第一個「有東西的」值:左邊有東西就回傳左邊,左邊是空的才去看右邊。
&& 是找第一個「空的」值:左邊是空的就回傳左邊,左邊有東西才去看右邊。
關鍵在於:如果左邊就能決定結果,|| 和 && 連看都不會去看右邊。
來看一個例子:
console.log(true || console.log("我被執行了"));
// → true左邊是 true(truthy),對 || 來說已經找到「有東西的」值了,不需要再看右邊。
所以右邊的 console.log("我被執行了") 完全沒有執行,畫面上不會出現「我被執行了」這段文字。
&& 也是一樣的道理:
console.log(false && console.log("我被執行了"));
// → false左邊是 false(falsy),對 && 來說已經找到「空的」值了,不需要再看右邊,右邊完全被跳過。
這個特性就叫做短路評估(short-circuit evaluation)。
為什麼這很重要?因為這代表右邊可以放一些「可能會出錯的操作」。
只要左邊就能決定結果,右邊就完全不會被執行,程式也不會因此當掉。
前面 && 安全檢查的用法,其實就是靠短路評估才能成立的。
那為什麼叫「短路」?
在電力學中,「短路」是指電流不經過原本該去的電器,而是走了一條更短、電阻更小的捷徑直接回到電源。
在程式設計中,這個術語借用了同樣的邏輯:「提早結束,不走完全程。」
正常來說,A && B 或 A || B 應該要把 A 和 B 兩邊都看過才對。
但短路機制讓程式可以「偷懶」:如果看完左邊就已經知道結果了,右邊就直接跳過,不浪費力氣去執行它。
條件運算子也有類似行為
條件運算子(? :)也有同樣的特性:
console.log(true ? "A" : "B");
// → "A"條件是 true,所以只有 "A" 會被評估,"B" 完全不會被碰到。
反過來也是:
console.log(false ? "A" : "B");
// → "B"條件是 false,所以 "A" 不會被碰到,只有 "B" 會被評估。
不管是 ||、&& 還是 ? :,都遵守同一個原則:已經能決定結果了,就不會再去碰多餘的部分。
當 && 和 || 同時出現:優先順序
前面的例子都只有一個 || 或一個 &&,但如果它們同時出現在同一行呢?
console.log("" && "World" || "Goodbye");這時候 JavaScript 要怎麼決定先算哪個?
跟數學裡「先乘除,後加減」一樣,&& 的優先順序比 || 高。
所以 JavaScript 會先幫你「分組」,把 && 的部分括起來:
console.log(("" && "World") || "Goodbye");我們一步一步來:
第一步,先算 "" && "World"。
還記得嗎?&& 是找第一個「空的」值。"" 是空字串,是 falsy,&& 找到了,直接回傳左邊的 ""。
現在變成:"" || "Goodbye"。
第二步,換 || 上場。|| 是找第一個「有東西的」值。"" 還是 falsy,不算有東西,所以 || 跳過它,回傳右邊的 "Goodbye"。
console.log("" && "World" || "Goodbye");
// → "Goodbye"再來看另一個例子:
console.log("Hi" || 0 && "Bye");一樣,&& 優先,所以 JavaScript 看到的是:
console.log("Hi" || (0 && "Bye"));但這次的情況不一樣,會顛覆你剛剛建立的直覺。
優先順序把它分組成 "Hi" || (0 && "Bye")。
照前面的邏輯,你可能以為要先算括號裡的 0 && "Bye"。
但其實不是這樣的。優先順序決定的只是「怎麼分組」,實際執行的時候,JavaScript 還是從左到右看的。
JavaScript 從左邊開始,先碰到 ||,然後看 || 的左邊:"Hi" 是 truthy。|| 是找第一個「有東西的」值,"Hi" 就是,任務完成,直接回傳 "Hi"。
因為短路評估,|| 已經決定結果了,右邊的 (0 && "Bye") 根本不會被執行。
console.log("Hi" || 0 && "Bye");
// → "Hi"那回頭看第一個例子:
console.log(("" && "World") || "Goodbye");JavaScript 從左到右看,先碰到 ||,要決定回傳左邊還是右邊。
但 || 的左邊不是一個單純的值,而是 "" && "World" 這個表達式。
所以 JavaScript 要先把它算完,才能知道 || 左邊到底是什麼:
"" && "World" // → ""算完之後,現在變成:
"" || "Goodbye" // → "Goodbye"所以 && 不是因為優先順序高就「搶先執行」,而是因為它剛好在 || 的左邊,JavaScript 從左到右走的時候自然會先碰到它。
所以:優先順序決定怎麼分組,執行順序還是從左到右,搭配短路評估決定哪些部分會被跳過。
小結
||和&&不是只能回傳布林值,它們會把選中的那一邊原封不動地回傳。||從左往右找第一個 truthy(有東西的)值;如果都找不到,就回傳最後一個值。&&從左往右找第一個 falsy(空的)值;如果都找不到,就回傳最後一個值。0、NaN、""、null、undefined這些值都是 falsy。||常被用來設定預設值;&&常被用來做安全檢查。&&的優先順序比||高,但優先順序只決定怎麼分組,實際執行還是從左到右,搭配短路評估決定哪些部分被跳過。- 短路評估代表只要左邊就能決定結果,右邊就不會被執行。
- 條件運算子(
? :)也有類似的短路行為。