JavaScript 中有四個邏輯運算符:||
(或),&&
(與),!
(非),??
(空值合並運算符)。本文我們先介紹前三個,在下壹篇文章中再詳細介紹 ??
運算符。
雖然它們被稱爲“邏輯”運算符,但這些運算符卻可以被應用于任意類型的值,而不僅僅是布爾值。它們的結果也同樣可以是任意類型。
讓我們來詳細看壹下。
兩個豎線符號表示“或”運算符:
result = a || b;
在傳統的編程中,邏輯或僅能夠操作布爾值。如果參與運算的任意壹個參數爲 true
,返回的結果就爲 true
,否則返回 false
。
在 JavaScript 中,邏輯運算符更加靈活強大。但是,首先讓我們看壹下操作數是布爾值的時候發生了什麽。
下面是四種可能的邏輯組合:
alert( true || true ); // true alert( false || true ); // true alert( true || false ); // true alert( false || false ); // false
正如我們所見,除了兩個操作數都是 false
的情況,結果都是 true
。
如果操作數不是布爾值,那麽它將會被轉化爲布爾值來參與運算。
例如,數字 1
被作爲 true
處理,數字 0
則被作爲 false
:
if (1 || 0) { // 工作原理相當于 if( true || false ) alert( 'truthy!' ); }
大多數情況下,邏輯或 ||
會被用在 if
語句中,用來測試是否有 任何 給定的條件爲 true
。
例如:
let hour = 9; if (hour < 10 || hour > 18) { alert( 'The office is closed.' ); }
我們可以傳入更多的條件:
let hour = 12; let isWeekend = true; if (hour < 10 || hour > 18 || isWeekend) { alert( 'The office is closed.' ); // 是周末 }
上文提到的邏輯處理多少有些傳統了。下面讓我們看看 JavaScript 的“附加”特性。
拓展的算法如下所示。
給定多個參與或運算的值:
result = value1 || value2 || value3;
或運算符 ||
做了如下的事情:
從左到右依次計算操作數。
處理每壹個操作數時,都將其轉化爲布爾值。如果結果是 true
,就停止計算,返回這個操作數的初始值。
如果所有的操作數都被計算過(也就是,轉換結果都是 false
),則返回最後壹個操作數。
返回的值是操作數的初始形式,不會做布爾轉換。
換句話說,壹個或運算 ||
的鏈,將返回第壹個真值,如果不存在真值,就返回該鏈的最後壹個值。
例如:
alert( 1 || 0 ); // 1(1 是真值) alert( null || 1 ); // 1(1 是第壹個真值) alert( null || 0 || 1 ); // 1(第壹個真值) alert( undefined || null || 0 ); // 0(都是假值,返回最後壹個值)
與“純粹的、傳統的、僅僅處理布爾值的或運算”相比,這個規則就引起了壹些很有趣的用法。
獲取變量列表或者表達式中的第壹個真值。
例如,我們有變量 firstName
、lastName
和 nickName
,都是可選的(即可以是 undefined,也可以是假值)。
我們用或運算 ||
來選擇有數據的那壹個,並顯示出來(如果沒有設置,則用 "Anonymous"
):
let firstName = ""; let lastName = ""; let nickName = "SuperCoder"; alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder
如果所有變量的值都爲假,結果就是 "Anonymous"
。
短路求值(Short-circuit evaluation)。
或運算符 ||
的另壹個用途是所謂的“短路求值”。
這指的是,||
對其參數進行處理,直到達到第壹個真值,然後立即返回該值,而無需處理其他參數。
如果操作數不僅僅是壹個值,而是壹個有副作用的表達式,例如變量賦值或函數調用,那麽這壹特性的重要性就變得顯而易見了。
在下面這個例子中,只會打印第二條信息:
true || alert("not printed"); false || alert("printed");
在第壹行中,或運算符 ||
在遇到 true
時立即停止運算,所以 alert
沒有運行。
有時,人們利用這個特性,只在左側的條件爲假時才執行命令。
兩個 & 符號表示 &&
與運算符:
result = a && b;
在傳統的編程中,當兩個操作數都是真值時,與運算返回 true
,否則返回 false
:
alert( true && true ); // true alert( false && true ); // false alert( true && false ); // false alert( false && false ); // false
帶有 if
語句的示例:
let hour = 12; let minute = 30; if (hour == 12 && minute == 30) { alert( 'Time is 12:30' ); }
就像或運算壹樣,與運算的操作數可以是任意類型的值:
if (1 && 0) { // 作爲 true && false 來執行 alert( "won't work, because the result is falsy" ); }
給出多個參加與運算的值:
result = value1 && value2 && value3;
與運算 &&
做了如下的事:
從左到右依次計算操作數。
在處理每壹個操作數時,都將其轉化爲布爾值。如果結果是 false
,就停止計算,並返回這個操作數的初始值。
如果所有的操作數都被計算過(例如都是真值),則返回最後壹個操作數。
換句話說,與運算返回第壹個假值,如果沒有假值就返回最後壹個值。
上面的規則和或運算很像。區別就是與運算返回第壹個假值,而或運算返回第壹個真值。
例如:
// 如果第壹個操作數是真值, // 與運算返回第二個操作數: alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5 // 如果第壹個操作數是假值, // 與運算將直接返回它。第二個操作數會被忽略 alert( null && 5 ); // null alert( 0 && "no matter what" ); // 0
我們也可以在壹行代碼上串聯多個值。查看第壹個假值是如何被返回的:
alert( 1 && 2 && null && 3 ); // null
如果所有的值都是真值,最後壹個值將會被返回:
alert( 1 && 2 && 3 ); // 3,最後壹個值
與運算 &&
在或運算 ||
之前進行
與運算 &&
的優先級比或運算 ||
要高。
所以代碼 a && b || c && d
跟 &&
表達式加了括號完全壹樣:(a && b) || (c && d)
。
不要用 ||
或 &&
來取代 if
有時候,有人會將與運算符 &&
作爲“簡化 if
”的壹種方式。
例如:
let x = 1; (x > 0) && alert( 'Greater than zero!' );
&&
右邊的代碼只有運算抵達到那裏才能被執行。也就是,當且僅當 (x > 0)
爲真。
所以我們基本可以類似地得到:
let x = 1; if (x > 0) alert( 'Greater than zero!' );
雖然使用 &&
寫出的變體看起來更短,但 if
更明顯,並且往往更具可讀性。因此,我們建議根據每個語法結構的用途來使用:如果我們想要 if
,就使用 if
;如果我們想要邏輯與,就使用 &&
。
感歎符號 !
表示布爾非運算符。
語法相當簡單:
result = !value;
邏輯非運算符接受壹個參數,並按如下運作:
將操作數轉化爲布爾類型:true/false
。
返回相反的值。
例如:
alert( !true ); // false alert( !0 ); // true
兩個非運算 !!
有時候用來將某個值轉化爲布爾類型:
alert( !!"non-empty string" ); // true alert( !!null ); // false
也就是,第壹個非運算將該值轉化爲布爾類型並取反,第二個非運算再次取反。最後我們就得到了壹個任意值到布爾值的轉化。
有壹個略顯冗長的方式也可以實現同樣的效果 —— 壹個內建的 Boolean
函數:
alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false
非運算符 !
的優先級在所有邏輯運算符裏面最高,所以它總是在 &&
和 ||
之前執行。
重要程度: 5
如下代碼將會輸出什麽?
alert( null || 2 || undefined );
結果是 2
,這是第壹個真值。
alert( null || 2 || undefined );
重要程度: 3
下面的代碼將會輸出什麽?
alert( alert(1) || 2 || alert(3) );
答案:首先是 1
,然後是 2
。
alert( alert(1) || 2 || alert(3) );
對 alert
的調用沒有返回值。或者說返回的是 undefined
。
第壹個或運算 ||
對它的左值 alert(1)
進行了計算。這就顯示了第壹條信息 1
。
函數 alert
返回了 undefined
,所以或運算繼續檢查第二個操作數以尋找真值。
第二個操作數 2
是真值,所以執行就中斷了。2
被返回,並且被外層的 alert 顯示。
這裏不會顯示 3
,因爲運算沒有抵達 alert(3)
。
重要程度: 5
下面這段代碼將會顯示什麽?
alert( 1 && null && 2 );
答案:null
,因爲它是列表中第壹個假值。
alert(1 && null && 2);
重要程度: 3
這段代碼將會顯示什麽?
alert( alert(1) && alert(2) );
答案:1
,然後 undefined
。
alert( alert(1) && alert(2) );
調用 alert
返回了 undefined
(它只展示消息,所以沒有有意義的返回值)。
因此,&&
計算了它左邊的操作數(顯示 1
),然後立即停止了,因爲 undefined
是壹個假值。&&
就是尋找假值然後返回它,所以運算結束。
重要程度: 5
結果將會是什麽?
alert( null || 2 && 3 || 4 );
答案:3
。
alert( null || 2 && 3 || 4 );
與運算 &&
的優先級比 ||
高,所以它第壹個被執行。
結果是 2 && 3 = 3
,所以表達式變成了:
null || 3 || 4
現在的結果就是第壹個真值:3
。
重要程度: 3
寫壹個 if
條件句來檢查 age
是否位于 14
到 90
的閉區間。
“閉區間”意味著,age
的值可以取 14
或 90
。
if (age >= 14 && age <= 90)
重要程度: 3
寫壹個 if
條件句,檢查 age
是否不位于 14
到 90
的閉區間。
創建兩個表達式:第壹個用非運算 !
,第二個不用。
第壹個表達式:
if (!(age >= 14 && age <= 90))
第二個表達式:
if (age < 14 || age > 90)
重要程度: 5
下面哪壹個 alert
將會被執行?
if(...)
語句內表達式的結果是什麽?
if (-1 || 0) alert( 'first' ); if (-1 && 0) alert( 'second' ); if (null || -1 && 1) alert( 'third' );
答案:第壹個和第三個將會被執行。
詳解:
// 執行。 // -1 || 0 的結果爲 -1,真值 if (-1 || 0) alert( 'first' ); // 不執行。 // -1 && 0 = 0,假值 if (-1 && 0) alert( 'second' ); // 執行 // && 運算的優先級比 || 高 // 所以 -1 && 1 先執行,給出如下運算鏈: // null || -1 && 1 -> null || 1 -> 1 if (null || -1 && 1) alert( 'third' );
重要程度: 3
實現使用 prompt
進行登錄校驗的代碼。
如果訪問者輸入 "Admin"
,那麽使用 prompt
引導獲取密碼,如果輸入的用戶名爲空或者按下了Esc鍵 —— 顯示 “Canceled”,如果是其他字符串 —— 顯示 “I don’t know you”。
密碼的校驗規則如下:
如果輸入的是 “TheMaster”,顯示 “Welcome!”,
其他字符串 —— 顯示 “Wrong password”,
空字符串或取消了輸入,顯示 “Canceled.”。
流程圖:
請使用嵌套的 if
塊。注意代碼整體的可讀性。
提示:將空字符串輸入,prompt 會獲取到壹個空字符串 ''
。Prompt 運行過程中,按下ESC鍵會得到 null
。
運行 demo
let userName = prompt("Who's there?", ''); if (userName === 'Admin') { let pass = prompt('Password?', ''); if (pass === 'TheMaster') { alert( 'Welcome!' ); } else if (pass === '' || pass === null) { alert( 'Canceled' ); } else { alert( 'Wrong password' ); } } else if (userName === '' || userName === null) { alert( 'Canceled' ); } else { alert( "I don't know you" ); }
請注意 if
塊中水平方向的縮進。技術上是非必需的,但會提升代碼的可讀性。