前端入门-JS学习笔记 2| 豆包MarsCode AI刷题

54 阅读6分钟

空值合并运算符‘??’

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

由于它对待 nullundefined 的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 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 的值。

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

我们想使用这些变量之一显示用户名,如果这些变量的值都是 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 不允许在这个位置

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

例如,下述代码中我们的循环使用了 ij,从 (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. (1) 行声明创建了函数,并把它放入到变量 sayHi
  2. (2) 行将 sayHi 复制到了变量 func。请注意:sayHi 后面没有括号。如果有括号,func = sayHi() 会把 sayHi() 的调用结果写进func,而不是 sayHi 函数 本身。
  3. 现在函数可以通过 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;
};