逻辑运算符
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( true || 'no matter what' ); //(true 是真值) alert( null || 1 ); // 1(1 是第一个真值) alert( null || 0 || 1 ); // 1(第一个真值) alert( undefined || null || 0 ); // 0(所有的转化结果都是 false,返回最后一个值)
与“纯粹的、传统的、仅仅处理布尔值的或运算”相比,这个规则就引起了一些很有趣的用法。
- 获取变量列表或者表达式的第一个真值。
假设我们有几个变量,它们可能包含某些数据或者是null/undefined
。我们需要选出第一个包含数据的变量。
我们可以这样应用或运算||
:
let currentUser = null; let defaultUser = "John"; let name = currentUser || defaultUser || "unnamed"; alert( name ); // 选出了 “John” — 第一个真值
- 如果
currentUser
和defaultUser
都是假值,那么结果就是"unnamed"
。 - 短路取值。
操作数不仅仅可以是值,还可以是任意表达式。或运算会从左到右计算并测试每个操作数。当找到第一个真值,计算就会停止,并返回这个值。这个过程就叫做“短路取值”,因为它尽可能地减少从左到右计算的次数。
当表达式作为第二个参数并且有一定的副作用(side effects),比如变量赋值的时候,短路取值的情况就清楚可见。
如果我们运行下面的例子,x
将不会被赋值:
let x; true || (x = 1); alert(x); // undefined,因为 (x = 1) 没有被执行
- 如果第一个参数是
false
,或运算将会继续,并计算第二个参数,也就会运行赋值操作。
let x; false || (x = 1); alert(x); // 1
- 赋值操作只是一个很简单的情况。可能有副作用,如果计算没有到达,副作用就不会发生。
正如我们所见,这种用法是“if
语句的简短方式”。第一个操作数被转化为布尔值,如果是假,那么第二个参数就会被执行。
大多数情况下,最好使用常规的if
语句,这样代码可读性更高,但是有时候这种方式会很简洁。
&&(与)
两个 & 符号表示 &&
与操作:
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
,就停止计算,并返回这个操作数的初始值。 - 如果所有的操作数都被计算过(也就是,转换结果都是
true
),则返回最后一个操作数。
换句话说,与操作符返回第一个假值,如果没有假值就返回最后一个值。
上面的规则和或运算很像。区别就是与运算返回第一个假值而或操作返回第一个真值。
例如:
// 如果第一个操作符是真值, // 与操作返回第二个操作数: 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
。
例如:
let x = 1; (x > 0) && alert( 'Greater than zero!' );
&&
右边的代码只有运算抵达到那里才能被执行。也就是,当且仅当 (x > 0)
返回了真值。
所以我们基本可以类似地得到:
let x = 1; if (x > 0) { alert( 'Greater than zero!' ); }
带 &&
的代码变体看上去更短。但是 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
非运算符 !
的优先级在所有逻辑运算符里面最高,所以它总是在 &&
和 ||
前执行。
作业题
先自己做题目再看答案。
1. 或运算的结果是什么?
重要程度:⭐️⭐️⭐️⭐️⭐️
如下代码将会输出什么?
alert( null || 2 || undefined );
答案:
结果是 2
,这是第一个真值。
alert( null || 2 || undefined );
2. 或运算和 alerts 的结果是什么?
重要程度:⭐️⭐️⭐
下面的代码将会输出什么?
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)
。
3. 与操作的结果是什么?
重要程度:⭐️⭐️⭐️⭐️⭐️
下面这段代码将会显示什么?
alert( 1 && null && 2 );
答案:
null
,因为它是列表中第一个假值。
alert( 1 && null && 2 );
4. 与运算连接的 alerts 的结果是什么?
重要程度:⭐️⭐️⭐
这段代码将会显示什么?
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
。
6. 检查值是否位于范围区间内
重要程度:⭐️⭐️⭐
写一个“if”条件句来检查 age
是否位于 14
到 90
的闭区间。
“闭区间”意味着,age
的值可以取 14
或 90
。
答案:
if (age >= 14 && age <= 90)
7. 检测值是否位于范围之外
重要程度:⭐️⭐️⭐
写一个 if
条件句,检查 age
是否不位于 14 到 90 的闭区间。
创建两个表达式:第一个用非运算 !
,第二个不用。
答案:
第一个表达式:
if (!(age >= 14 && age <= 90))
第二个表达式:
if (age < 14 || age > 90)
8. 一个关于 "if" 的问题
重要程度:⭐️⭐️⭐️⭐️⭐️
下面哪一个 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' );
9. 登陆验证
重要程度:⭐️⭐️⭐
实现使用 prompt
进行登陆校验的代码。
如果访问者输入 "Admin"
,那么使用 prompt
引导获取密码,如果输入的用户名为空或者按下了 key:Esc
键 —— 显示 "Canceled",如果是其他字符串 —— 显示 "I don't know you"。
密码的校验规则如下:
- 如果输入的是 "TheMaster",显示 "Welcome!",
- 其他字符串 —— 显示 "Wrong password",
- 空字符串或取消了输入,显示 "Canceled."。
流程图:
请使用嵌套的 if
块。注意代码整体的可读性。
提示:将空字符串输入,prompt 会获取到一个空字符串 ''
。Prompt 运行过程中,按下 key:ESC
键会得到 null
。
答案:
代码如下:
let userName = prompt("Who's there?", ''); if (userName == 'Admin') { let pass = prompt('Password?', ''); if (pass == 'TheMaster') { alert( 'Welcome!' ); } elseif (pass == '' || pass == null) { alert( 'Canceled' ); } else { alert( 'Wrong password' ); } } elseif (userName == '' || userName == null) { alert( 'Canceled' ); } else { alert( "I don't know you" ); }
请注意 if
块中水平方向的缩进。技术上是非必需的,但会增加代码的可读性。