天天看点

JavaScript 基础知识 3

#空值合并运算符 '??'

既不是 null 也不是 undefined 的表达式称为“已定义的(defined)”。

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

a ?? b 的结果是:

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

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

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

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

let firstname = null;

let lastname = null;

let nickname = "supercoder";

// 显示第一个已定义的值:

alert(firstname ?? lastname ?? nickname ?? "anonymous"); // supercoder

与 || 比较

或运算符 || 可以以与 ?? 运算符相同的方式使用。

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

它们之间重要的区别是:

|| 返回第一个 真 值。

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

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

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

let height = 0;

alert(height || 100); // 100

alert(height ?? 100); // 0

优先级

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

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

let height = null;

let width = null;

// 重要:使用括号

let area = (height ?? 100) * (width ?? 50);

alert(area); // 5000

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

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

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

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

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

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

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

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

alert(x); // 2

总结

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

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

// 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100

height = height ?? 100;

?? 运算符的优先级非常低,仅略高于 ? 和 =,因此在表达式中使用它时请考虑添加括号。

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

#循环:while 和 for

循环 是一种重复运行同一代码的方法。

“while” 循环

while 循环的语法如下:

while (condition) {

// 代码

// 所谓的“循环体”

}

当 condition 为真时,执行循环体的 code。

任何表达式或变量都可以是循环条件,而不仅仅是比较。在 while 中的循环条件会被计算,计算结果会被转化为布尔值。

例如,while (i != 0) 可简写为 while (i):

单行循环体不需要大括号

如果循环体只有一条语句,则可以省略大括号 {…}:

let i = 3;

while (i) alert(i--);

“do…while” 循环

使用 do..while 语法可以将条件检查移至循环体 下面:

do {

// 循环体

} while (condition);

for” 循环

for 循环更加复杂,但它是最常使用的循环形式。

for 循环看起来就像这样:

for (begin; condition; step) {

// ……循环体……

我们通过示例来了解一下这三个部分的含义。下述循环从 i 等于 0 到 3(但不包括 3)运行 alert(i):

for (let i = 0; i < 3; i++) { // 结果为 0、1、2

alert(i);

跳出循环 break

通常条件为假时,循环会终止。

但我们随时都可以使用 break 指令强制退出。

例如,下面这个循环要求用户输入一系列数字,在输入的内容不是数字时“终止”循环。

let sum = 0;

while (true) {

let value = +prompt("enter a number", '');

if (!value) break; // (*)

sum += value;

alert( 'sum: ' + sum );

如果用户输入空行或取消输入,在 (*) 行的 break 指令会被激活。它立刻终止循环,将控制权传递给循环后的第一行,即,alert。

    继续下一次迭代 continue

continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。

如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。

下面这个循环使用 continue 来只输出奇数:

for (let i = 0; i < 10; i++) {

//如果为真,跳过循环体的剩余部分。

if (i % 2 == 0) continue;

alert(i); // 1,然后 3,5,7,9

对于偶数的 i 值,continue 指令会停止本次循环的继续执行,将控制权传递给下一次 for 循环的迭代(使用下一个数字)。因此 alert 仅被奇数值调用。

触发continue后,continue后的代码不会再执行,而是启动下一次for循环。

    break/continue 标签

有时候我们需要从一次从多层嵌套的循环中跳出来。

例如,下述代码中我们的循环使用了 i 和 j,从 (0,0) 到 (3,3) 提示坐标 (i, j):

for (let i = 0; i < 3; i++) {

for (let j = 0; j < 3; j++) {

let input = prompt(`value at coords (${i},${j})`, '');

// 如果我想从这里退出并直接执行 alert('done!')

alert('done!');

我们需要提供一种方法,以在用户取消输入时来停止这个过程。

在 input 之后的普通 break 只会打破内部循环。这还不够 —— 标签可以实现这一功能!

标签 是在循环之前带有冒号的标识符:

labelname: for (...) {

...

break <labelname> 语句跳出循环至标签处:

outer: for (let i = 0; i < 3; i++) {

// 如果是空字符串或被取消,则中断并跳出这两个循环。

if (!input) break outer; // (*)

// 用得到的值做些事……

上述代码中,break outer 向上寻找名为 outer 的标签并跳出当前循环。

因此,控制权直接从 (*) 转至 alert('done!')。

我们还可以将标签移至单独一行:

outer:

for (let i = 0; i < 3; i++) { ... }

continue 指令也可以与标签一起使用。在这种情况下,执行跳转到标记循环的下一次迭代。