空值合并运算符‘??’
空值合并运算符(nullish coalescing operator)的写法为两个问号 ??。
由于它对待 null 和 undefined 的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 null 也不是 undefined 时,我们将其称为“已定义的(defined)”。
a ?? b 的结果是:
- 如果
a是已定义的,则结果为a, - 如果
a不是已定义的,则结果为b。
使用我们已知的运算符重写 result = a ?? b,像这样:
result = (a !== null && a !== undefined) ? a : b;
?? 的常见使用场景是提供默认值。
例如,在这里,如果 user 的值不为 null/undefined 则显示 user,否则显示 匿名:
let user;
alert(user ?? "匿名"); // 匿名(user 未定义)
在下面这个例子中,我们将一个名字赋值给了 user:
let user = "John";
alert(user ?? "匿名"); // John(user 已定义)
我们还可以使用 ?? 序列从一系列的值中选择出第一个非 null/undefined 的值。
假设我们在变量 firstName、lastName 或 nickName 中存储着一个用户的数据。如果用户决定不填写相应的值,则所有这些变量的值都可能是未定义的。
我们想使用这些变量之一显示用户名,如果这些变量的值都是 null/undefined,则显示 “匿名”。
让我们使用 ?? 运算符来实现这一需求:
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// 显示第一个已定义的值:
alert(firstName ?? lastName ?? nickName ?? "匿名"); // Supercoder
循环:while和for
while 循环的语法如下:
while (condition) {
// 代码
// 所谓的“循环体”
}
使用 do..while 语法可以将条件检查移至循环体 下面:
do {
// 循环体
} while (condition);
for 循环看起来就像这样:
for (begin; condition; step) {
// ……循环体……
}
通常条件为假时,循环会终止。
但我们随时都可以使用 break 指令强制退出。
continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。
如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。
请注意非表达式的语法结构不能与三元运算符 ? 一起使用。特别是 break/continue 这样的指令是不允许这样使用的。
例如,我们使用如下代码:
if (i > 5) {
alert(i);
} else {
continue;
}
……用问号重写:
(i > 5) ? alert(i) : continue; // 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++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果是空字符串或被取消,则中断并跳出这两个循环。
if (!input) break outer; // (*)
// 用得到的值做些事……
}
}
alert('Done!');
上述代码中,break outer 向上寻找名为 outer 的标签并跳出当前循环。
因此,控制权直接从 (*) 转至 alert('Done!')。
我们还可以将标签移至单独一行:
outer:
for (let i = 0; i < 3; i++) { ... }
switch
switch 的例子(高亮的部分是执行的 case 部分):
let a = 2 + 2;
switch (a) {
case 3:
alert( 'Too small' );
break;
case 4:
alert( 'Exactly!' );
break;
case 5:
alert( 'Too big' );
break;
default:
alert( "I don't know such values" );
}
函数
使用 函数声明 创建函数。
看起来就像这样:
function showMessage() {
alert( 'Hello everyone!' );
}
function 关键字首先出现,然后是 函数名,然后是括号之间的 参数 列表(用逗号分隔,在上述示例中为空,我们将在接下来的示例中看到),最后是花括号之间的代码(即“函数体”)。
function name(parameter1, parameter2, ... parameterN) {
...body...
}
函数声明:
function sayHi() {
alert( "Hello" );
}
另一种创建函数的语法称为 函数表达式。
它允许我们在任何表达式的中间创建一个新函数。
例如:
let sayHi = function() {
alert( "Hello" );
};
在这里我们可以看到变量 sayHi 得到了一个值,新函数 function() { alert("Hello"); }。
由于函数创建发生在赋值表达式的上下文中(在 = 的右侧),因此这是一个 函数表达式。
请注意,function 关键字后面没有函数名。函数表达式允许省略函数名。
这里我们立即将它赋值给变量,所以上面的两个代码示例的含义是一样的:“创建一个函数并将其放入变量 sayHi 中”。
在更多更高阶的情况下,稍后我们会遇到,可以创建一个函数并立即调用,或者安排稍后执行,而不是存储在任何地方,因此保持匿名。
在某种意义上说一个函数是一个特殊值,我们可以像 sayHi() 这样调用它。
但它依然是一个值,所以我们可以像使用其他类型的值一样使用它。
我们可以复制函数到其他变量:
function sayHi() { // (1) 创建
alert( "Hello" );
}
let func = sayHi; // (2) 复制
func(); // Hello // (3) 运行复制的值(正常运行)!
sayHi(); // Hello // 这里也能运行(为什么不行呢)
解释一下上段代码发生的细节:
(1)行声明创建了函数,并把它放入到变量sayHi。(2)行将sayHi复制到了变量func。请注意:sayHi后面没有括号。如果有括号,func = sayHi()会把sayHi()的调用结果写进func,而不是sayHi函数 本身。- 现在函数可以通过
sayHi()和func()两种方式进行调用。
我们也可以在第一行中使用函数表达式来声明 sayHi:
let sayHi = function() { // (1) 创建
alert( "Hello" );
};
let func = sayHi;
// ...
这两种声明的函数是一样的。
箭头函数,基础知识
创建函数还有另外一种非常简单的语法,并且这种方法通常比函数表达式更好。
它被称为“箭头函数”,因为它看起来像这样:
let func = (arg1, arg2, ..., argN) => expression;
这里创建了一个函数 func,它接受参数 arg1..argN,然后使用参数对右侧的 expression 求值并返回其结果。
换句话说,它是下面这段代码的更短的版本:
let func = function(arg1, arg2, ..., argN) {
return expression;
};