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 块中水平方向的缩进。技术上是非必需的,但会提升代码的可读性。