逻辑运算符与练习

运算符优先级

JavaScript 中有三个逻辑运算符:||(或),&&(与),!(非)。

虽然它们被称为“逻辑”运算符,但这些运算符却可以被应用于任意类型的值,而不仅仅是布尔值。它们的结果也同样可以是任意类型。

让我们来详细看一下。

!大于&&大于||

与运算 && 在或运算 || 之前进行

与运算 && 的优先级比或运算 || 要高。

所以代码 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 = a || b;

下面是四种可能的逻辑组合:

alert( true || true );   // true
alert( false || true );  // true
alert( true || false );  // true
alert( false || false ); // false

正如我们所见,除了两个操作数都是 false 的情况,结果都是 true

如果操作数不是布尔值,那么它将会被转化为布尔值来参与运算。

获取变量列表或者表达式中的第一个真值

例如,我们有变量 firstNamelastNamenickName,都是可选的(即可以是 undefined,也可以是假值)。

我们用或运算 || 来选择有数据的那一个,并显示出来(如果没有设置,则用 "Anonymous"):

let firstName = "";
let lastName = "";
let nickName = "SuperCoder";

alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder

如果所有变量的值都为假,结果就是 "Anonymous"

或运算符 || 做了如下的事情:

  • 从左到右依次计算操作数。
  • 处理每一个操作数时,都将其转化为布尔值。如果结果是 true,就停止计算,返回这个操作数的初始值。
  • 如果所有的操作数都被计算过(也就是,转换结果都是 false),则返回最后一个操作数。

短路求值

或运算符 || 的另一个用途是所谓的“短路求值”。

这指的是,|| 对其参数进行处理,直到达到第一个真值,然后立即返回该值,而无需处理其他参数。

如果操作数不仅仅是一个值,而是一个有副作用的表达式,例如变量赋值或函数调用,那么这一特性的重要性就变得显而易见了。

在下面这个例子中,只会打印第二条信息:

true || alert("not printed");
false || alert("printed");

在第一行中,或运算符 || 在遇到 true 时立即停止运算,所以 alert 没有运行。

有时,人们利用这个特性,只在左侧的条件为假时才执行命令。

&&与

与运算寻找第一个假值

给出多个参加与运算的值:

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,最后一个值

!非

感叹符号 ! 表示布尔非运算符。

感叹符号 ! 表示布尔非运算符。

语法相当简单:

result = !value;

逻辑非运算符接受一个参数,并按如下运作:

  1. 将操作数转化为布尔类型:true/false
  2. 返回相反的值。

例如:

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

非运算符 ! 的优先级在所有逻辑运算符里面最高,所以它总是在 &&|| 之前执行。

空值合并运算符 ??

空值合并运算符(nullish coalescing operator)的写法为两个问号 ??

a ?? b 的结果是:

  • 如果 a 是已定义的,则结果为 a
  • 如果 a 不是已定义的,则结果为 b

换句话说,如果第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数。

空值合并运算符并不是什么全新的东西。它只是一种获得两者中的第一个“已定义的”值的不错的语法。

我们可以使用我们已知的运算符重写 result = a ?? b,像这样:

result = (a !== null && a !== undefined) ? a : b;

通常 ?? 的使用场景是,为可能是未定义的变量提供一个默认值。

例如,在这里,如果 user 是未定义的,我们则显示 Anonymous

let user;

alert(user ?? "Anonymous"); // Anonymous

当然,如果 user 的值为除 null/undefined 外的任意值,那么我们看到的将是它:

let user = "John";

alert(user ?? "Anonymous"); // John

我们还可以使用 ?? 序列从一系列的值中选择出第一个非 null/undefined 的值。

假设我们在变量 firstNamelastNamenickName 中存储着一个用户的数据。如果用户决定不输入值,则所有这些变量的值都可能是未定义的。

我们想使用这些变量之一显示用户名,如果这些变量的值都是未定义的,则显示 “Anonymous”。

让我们使用 ?? 运算符来实现这一需求:

let firstName = null;
let lastName = null;
let nickName = "Supercoder";

// 显示第一个已定义的值:
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder

与 || 比较

例如,在上面的代码中,我们可以用 || 替换掉 ??,也可以获得相同的结果:

let firstName = null;
let lastName = null;
let nickName = "Supercoder";

// 显示第一个真值:
alert(firstName || lastName || nickName || "Anonymous"); // Supercoder

|| 运算符自 JavaScript 诞生就存在,因此开发者长期将其用于这种目的。

另一方面,空值合并运算符 ?? 是最近才被添加到 JavaScript 中的,它的出现是因为人们对 || 不太满意。

它们之间重要的区别是:

  • || 返回第一个 值。
  • ?? 返回第一个 已定义的 值。

换句话说,|| 无法区分 false0、空字符串 ""null/undefined。它们都一样 —— 假值(falsy values)。如果其中任何一个是 || 的第一个参数,那么我们将得到第二个参数作为结果。

不过在实际中,我们可能只想在变量的值为 null/undefined 时使用默认值。也就是说,当该值确实未知或未被设置时。

例如,考虑下面这种情况:

let height = 0;

alert(height || 100); // 100
alert(height ?? 100); // 0
  • height || 100
    

    首先会检查

height


是否为一个假值,发现它确实是。

- 所以,结果为第二个参数,`100`。

- ```
height ?? 100

首先会检查

height

是否为

null/undefined

,发现它不是。

  • 所以,结果为 height 的原始值,0

如果高度 0 为有效值,则不应将其替换为默认值,所以 ?? 能够得出正确的结果。

优先级

?? 运算符的优先级相当低:在 MDN table 中为 5。因此,??=? 之前计算,但在大多数其他运算符(例如,+*)之后计算。

因此,如果我们需要在还有其他运算符的表达式中使用 ?? 进行取值,需要考虑加括号:

let height = null;
let width = null;

// 重要:使用括号
let area = (height ?? 100) * (width ?? 50);

alert(area); // 5000

否则,如果我们省略了括号,则由于 * 的优先级比 ?? 高,它会先执行,进而导致错误的结果。

// 没有括号
let area = height ?? 100 * width ?? 50;

// ……与下面这行代码的计算方式相同(应该不是我们所期望的):
let area = height ?? (100 * width) ?? 50;

?? 与 && 或 || 一起使用

出于安全原因,JavaScript 禁止将 ?? 运算符与 &&|| 运算符一起使用,除非使用括号明确指定了优先级。

下面的代码会触发一个语法错误:

let x = 1 && 2 ?? 3; // Syntax error

这个限制无疑是值得商榷的,但它被添加到语言规范中是为了避免人们从 || 切换到 ?? 时的编程错误。

可以明确地使用括号来解决这个问题:

let x = (1 && 2) ?? 3; // 正常工作了

alert(x); // 2
  • 空值合并运算符 ?? 提供了一种从列表中选择第一个“已定义的”值的简便方式。

    它被用于为变量分配默认值:

    // 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100
    height = height ?? 100;
    
  • ?? 运算符的优先级非常低,仅略高于 ?=,因此在表达式中使用它时请考虑添加括号。

  • 如果没有明确添加括号,不能将其与 ||&& 一起使用。

逻辑运算练习 (&& 运算的优先级比 || 高)

或运算的结果是什么?

重要程度: 5

如下代码将会输出什么?

alert( null || 2 || undefined );

解决方案

结果是 2,这是第一个真值。

alert( null || 2 || undefined );

或运算和 alerts 的结果是什么?

重要程度: 3

下面的代码将会输出什么?

alert( alert(1) || 2 || alert(3) );

解决方案

答案:首先是 1,然后是 2

alert( alert(1) || 2 || alert(3) );

alert 的调用没有返回值。或者说返回的是 undefined

  1. 第一个或运算 || 对它的左值 alert(1) 进行了计算。这就显示了第一条信息 1
  2. 函数 alert 返回了 undefined,所以或运算继续检查第二个操作数以寻找真值。
  3. 第二个操作数 2 是真值,所以执行就中断了。2 被返回,并且被外层的 alert 显示。

这里不会显示 3,因为运算没有抵达 alert(3)

与操作的结果是什么?

重要程度: 5

下面这段代码将会显示什么?

alert( 1 && null && 2 );

解决方案

答案:null,因为它是列表中第一个假值。

alert( 1 && null && 2 );

与运算连接的 alerts 的结果是什么?

重要程度: 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 是否位于 1490 的闭区间。

“闭区间”意味着,age 的值可以取 1490

解决方案

if (age >= 14 && age <= 90)

检查值是否位于范围之外

重要程度: 3

写一个 if 条件句,检查 age 是否不位于 1490 的闭区间。

创建两个表达式:第一个用非运算 !,第二个不用。

解决方案

第一个表达式:

if (!(age >= 14 && age <= 90))

第二个表达式:

if (age < 14 || age > 90)

一个关于 “if” 的问题

重要程度: 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

解决方案

    let userName = prompt('你是谁?', '');
        if (userName === 'Admin') {
            let pass = prompt('请输入密码?', '');
            if (pass === '123456') {
                alert('Welcome!');
            } else if (pass === '' || pass === null) {
                alert('请输入密码');
            } else {
                alert('密码错误');
            }

        } else if (userName === '' || userName === null) {
            alert('请输入用户名');
        } else {
            alert('用户名输入错误');
        }

请注意 if 块中水平方向的缩进。技术上是非必需的,但会增加代码的可读性。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值