JavaScript 权威指南第七版(GPT 重译)(二)(3)

简介: JavaScript 权威指南第七版(GPT 重译)(二)

JavaScript 权威指南第七版(GPT 重译)(二)(2)https://developer.aliyun.com/article/1485293

4.14 总结

本章涵盖了各种主题,并且这里有很多参考资料,您可能希望在未来继续学习 JavaScript 时重新阅读。然而,需要记住的一些关键点是:

  • 表达式是 JavaScript 程序的短语。
  • 任何表达式都可以评估为 JavaScript 值。
  • 表达式除了产生一个值外,还可能具有副作用(如变量赋值)。
  • 简单表达式,如文字,变量引用和属性访问,可以与运算符结合以产生更大的表达式。
  • JavaScript 定义了用于算术,比较,布尔逻辑,赋值和位操作的运算符,以及一些其他运算符,包括三元条件运算符。
  • JavaScript + 运算符用于添加数字和连接字符串。
  • 逻辑运算符&&||具有特殊的“短路”行为,有时只评估它们的一个参数。常见的 JavaScript 习语要求您了解这些运算符的特殊行为。

第五章:语句

第四章将表达式描述为 JavaScript 短语。按照这个类比,语句是 JavaScript 句子或命令。就像英语句子用句号终止并用句号分隔开一样,JavaScript 语句用分号终止(§2.6)。表达式被评估以产生一个值,但语句被执行以使某事发生。

使某事发生的一种方法是评估具有副作用的表达式。具有副作用的表达式,如赋值和函数调用,可以独立作为语句存在,当以这种方式使用时被称为表达式语句。另一类语句是声明语句,它声明新变量并定义新函数。

JavaScript 程序只不过是一系列要执行的语句。默认情况下,JavaScript 解释器按照它们编写的顺序一个接一个地执行这些语句。改变这种默认执行顺序的另一种方法是使用 JavaScript 中的一些语句或控制结构

条件语句

诸如ifswitch这样的语句根据表达式的值使 JavaScript 解释器执行或跳过其他语句

循环

诸如whilefor这样重复执行其他语句的语句

跳转

诸如breakreturnthrow这样的语句会导致解释器跳转到程序的另一个部分

接下来的章节描述了 JavaScript 中的各种语句并解释了它们的语法。表 5-1 在本章末尾总结了语法。JavaScript 程序只不过是一系列语句,用分号分隔开,因此一旦熟悉了 JavaScript 的语句,就可以开始编写 JavaScript 程序。

5.1 表达式语句

JavaScript 中最简单的语句是具有副作用的表达式。这种语句在第四章中有所展示。赋值语句是表达式语句的一个主要类别。例如:

greeting = "Hello " + name;
i *= 3;

递增和递减运算符++--与赋值语句相关。它们具有改变变量值的副作用,就像执行了一个赋值一样:

counter++;

delete 运算符的重要副作用是删除对象属性。因此,它几乎总是作为语句使用,而不是作为更大表达式的一部分:

delete o.x;

函数调用是另一种重要的表达式语句。例如:

console.log(debugMessage);
displaySpinner(); // A hypothetical function to display a spinner in a web app.

这些函数调用是表达式,但它们具有影响主机环境或程序状态的副作用,并且在这里被用作语句。如果一个函数没有任何副作用,那么调用它就没有意义,除非它是更大表达式或赋值语句的一部分。例如,你不会仅仅计算余弦值然后丢弃结果:

Math.cos(x);

但你可能会计算值并将其赋给一个变量以备将来使用:

cx = Math.cos(x);

请注意,这些示例中的每行代码都以分号结束。

5.2 复合语句和空语句

就像逗号运算符(§4.13.7)将多个表达式组合成一个单一表达式一样,语句块将多个语句组合成一个复合语句。语句块只是一系列语句被花括号包围起来。因此,以下行作为单个语句,并可以在 JavaScript 需要单个语句的任何地方使用:

{
    x = Math.PI;
    cx = Math.cos(x);
    console.log("cos(π) = " + cx);
}

关于这个语句块有几点需要注意。首先,它以分号结束。块内的原始语句以分号结束,但块本身不以分号结束。其次,块内的行相对于包围它们的花括号缩进。这是可选的,但它使代码更易于阅读和理解。

就像表达式经常包含子表达式一样,许多 JavaScript 语句包含子语句。形式上,JavaScript 语法通常允许单个子语句。例如,while循环语法包括一个作为循环体的单个语句。使用语句块,您可以在这个单个允许的子语句中放置任意数量的语句。

复合语句允许您在 JavaScript 语法期望单个语句的地方使用多个语句。空语句则相反:它允许您在期望一个语句的地方不包含任何语句。空语句如下所示:

;

当执行空语句时,JavaScript 解释器不会采取任何操作。空语句偶尔在您想要创建一个空循环体的循环时很有用。考虑以下for循环(for循环将在§5.4.3 中介绍):

// Initialize an array a
for(let i = 0; i < a.length; a[i++] = 0) ;

在这个循环中,所有工作都由表达式a[i++] = 0完成,不需要循环体。然而,JavaScript 语法要求循环体作为一个语句,因此使用了一个空语句——只是一个裸分号。

请注意,在for循环、while循环或if语句的右括号后意外包含分号可能导致难以检测的令人沮丧的错误。例如,以下代码可能不会按照作者的意图执行:

if ((a === 0) || (b === 0));  // Oops! This line does nothing...
    o = null;                 // and this line is always executed.

当您有意使用空语句时,最好以一种清晰表明您是有意这样做的方式对代码进行注释。例如:

for(let i = 0; i < a.length; a[i++] = 0) /* empty */ ;

5.3 条件语句

条件语句根据指定表达式的值执行或跳过其他语句。这些语句是您代码的决策点,有时也被称为“分支”。如果想象一个 JavaScript 解释器沿着代码路径执行,条件语句是代码分支成两个或多个路径的地方,解释器必须选择要遵循的路径。

以下小节解释了 JavaScript 的基本条件语句if/else,并介绍了更复杂的多路分支语句switch

5.3.1 if

if语句是允许 JavaScript 做出决策的基本控制语句,更准确地说,是有条件地执行语句。该语句有两种形式。第一种是:

if (*`expression`*)
    *`statement`*

在这种形式中,expression被评估。如果结果值为真值,将执行statement。如果expression为假值,则不执行statement。(有关真值和假值的定义,请参见§3.4。)例如:

if (username == null)       // If username is null or undefined,
    username = "John Doe";  // define it

或者类似地:

// If username is null, undefined, false, 0, "", or NaN, give it a new value
if (!username) username = "John Doe";

请注意,围绕expression的括号是if语句语法的必需部分。

JavaScript 语法要求在if关键字和括号表达式之后有一个语句,但您可以使用语句块将多个语句组合成一个。因此,if语句也可能如下所示:

if (!address) {
    address = "";
    message = "Please specify a mailing address.";
}

第二种形式的if语句引入了一个else子句,当expressionfalse时执行。其语法如下:

if (*`expression`*)
    *`statement1`*
else
    *`statement2`*

该语句形式在expression为真值时执行statement1,在expression为假值时执行statement2。例如:

if (n === 1)
    console.log("You have 1 new message.");
else
    console.log(`You have ${n} new messages.`);

当您有嵌套的带有else子句的if语句时,需要谨慎确保else子句与适当的if语句配对。考虑以下行:

i = j = 1;
k = 2;
if (i === j)
    if (j === k)
        console.log("i equals k");
else
    console.log("i doesn't equal j");    // WRONG!!

在这个例子中,内部的if语句形成了外部if语句语法允许的单个语句。不幸的是,不清楚(除了缩进给出的提示外)else与哪个if配对。而且在这个例子中,缩进是错误的,因为 JavaScript 解释器实际上将前一个例子解释为:

if (i === j) {
    if (j === k)
        console.log("i equals k");
    else
        console.log("i doesn't equal j");    // OOPS!
}

JavaScript(与大多数编程语言一样)的规则是,默认情况下else子句是最近的if语句的一部分。为了使这个例子不那么模棱两可,更容易阅读、理解、维护和调试,您应该使用花括号:

if (i === j) {
    if (j === k) {
        console.log("i equals k");
    }
} else {  // What a difference the location of a curly brace makes!
    console.log("i doesn't equal j");
}

许多程序员习惯将 ifelse 语句的主体(以及其他复合语句,如 while 循环)放在花括号中,即使主体只包含一个语句。始终如此可以防止刚才显示的问题,我建议你采用这种做法。在这本印刷书中,我非常重视保持示例代码的垂直紧凑性,并且并不总是遵循自己在这个问题上的建议。

5.3.2 else if

if/else 语句评估一个表达式并根据结果执行两个代码块中的一个。但是当你需要执行多个代码块中的一个时怎么办?一种方法是使用 else if 语句。else if 实际上不是一个 JavaScript 语句,而只是一个经常使用的编程习惯,当使用重复的 if/else 语句时会出现:

if (n === 1) {
    // Execute code block #1
} else if (n === 2) {
    // Execute code block #2
} else if (n === 3) {
    // Execute code block #3
} else {
    // If all else fails, execute block #4
}

这段代码没有什么特别之处。它只是一系列 if 语句,每个后续的 if 都是前一个语句的 else 子句的一部分。使用 else if 习惯比在其语法上等效的完全嵌套形式中编写这些语句更可取,也更易读:

if (n === 1) {
    // Execute code block #1
}
else {
    if (n === 2) {
        // Execute code block #2
    }
    else {
        if (n === 3) {
            // Execute code block #3
        }
        else {
            // If all else fails, execute block #4
        }
    }
}

5.3.3 switch

if 语句会导致程序执行流程的分支,你可以使用 else if 习惯来执行多路分支。然而,当所有分支都依赖于相同表达式的值时,这并不是最佳解决方案。在这种情况下,多次在多个 if 语句中评估该表达式是浪费的。

switch 语句正好处理这种情况。switch 关键字后跟着括号中的表达式和花括号中的代码块:

switch(*`expression`*) {
    *`statements`*
}

然而,switch 语句的完整语法比这更复杂。代码块中的各个位置都用 case 关键字标记,后跟一个表达式和一个冒号。当 switch 执行时,它计算表达式的值,然后寻找一个 case 标签,其表达式的值与之相同(相同性由 === 运算符确定)。如果找到一个匹配值的 case,它会从标记为 case 的语句开始执行代码块。如果找不到具有匹配值的 case,它会寻找一个标记为 default: 的语句。如果没有 default: 标签,switch 语句会跳过整个代码块。

switch 是一个很难解释的语句;通过一个例子,它的操作会变得更加清晰。下面的 switch 语句等同于前一节中展示的重复的 if/else 语句:

switch(n) {
case 1:                        // Start here if n === 1
    // Execute code block #1.
    break;                     // Stop here
case 2:                        // Start here if n === 2
    // Execute code block #2.
    break;                     // Stop here
case 3:                        // Start here if n === 3
    // Execute code block #3.
    break;                     // Stop here
default:                       // If all else fails...
    // Execute code block #4.
    break;                     // Stop here
}

注意这段代码中每个 case 结尾使用的 break 关键字。break 语句会在本章后面描述,它会导致解释器跳出(或“中断”)switch 语句并继续执行后面的语句。switch 语句中的 case 子句只指定所需代码的起始点;它们不指定任何结束点。在没有 break 语句的情况下,switch 语句会从与其表达式值匹配的 case 标签开始执行其代码块,并继续执行语句直到达到代码块的末尾。在极少数情况下,编写“穿透”从一个 case 标签到下一个的代码是有用的,但 99% 的情况下,你应该小心地用 break 语句结束每个 case。(然而,在函数内部使用 switch 时,你可以使用 return 语句代替 break 语句。两者都用于终止 switch 语句并防止执行穿透到下一个 case。)

这里是 switch 语句的一个更加现实的例子;它根据值的类型将值转换为字符串:

function convert(x) {
    switch(typeof x) {
    case "number":            // Convert the number to a hexadecimal integer
        return x.toString(16);
    case "string":            // Return the string enclosed in quotes
        return '"' + x + '"';
    default:                  // Convert any other type in the usual way
        return String(x);
    }
}

请注意,在前两个示例中,case关键字分别后跟数字和字符串字面量。这是switch语句在实践中最常用的方式,但请注意,ECMAScript 标准允许每个case后跟任意表达式。

switch语句首先评估跟在switch关键字后面的表达式,然后按照它们出现的顺序评估case表达式,直到找到匹配的值。匹配的情况是使用===身份运算符确定的,而不是==相等运算符,因此表达式必须在没有任何类型转换的情况下匹配。

因为并非每次执行switch语句时都会评估所有case表达式,所以应避免使用包含函数调用或赋值等副作用的case表达式。最安全的做法是将case表达式限制为常量表达式。

如前所述,如果没有case表达式与switch表达式匹配,switch语句将从标记为default:的语句处开始执行其主体。如果没有default:标签,则switch语句将完全跳过其主体。请注意,在所示示例中,default:标签出现在switch主体的末尾,跟在所有case标签后面。这是一个逻辑和常见的位置,但实际上它可以出现在语句主体的任何位置。

5.4 循环

要理解条件语句,我们可以想象 JavaScript 解释器通过源代码的分支路径。循环语句是将该路径弯回自身以重复代码部分的语句。JavaScript 有五个循环语句:whiledo/whileforfor/of(及其for/await变体)和for/in。以下各小节依次解释每个循环语句。循环的一个常见用途是遍历数组元素。§7.6 详细讨论了这种循环,并涵盖了 Array 类定义的特殊循环方法。

5.4.1 while

就像if语句是 JavaScript 的基本条件语句一样,while语句是 JavaScript 的基本循环语句。它的语法如下:

while (*`expression`*)
    *`statement`*

要执行while语句,解释器首先评估expression。如果表达式的值为假值,则解释器跳过作为循环体的statement并继续执行程序中的下一条语句。另一方面,如果expression为真值,则解释器执行statement并重复,跳回循环的顶部并再次评估expression。另一种说法是,解释器在expression为真值时重复执行statement。请注意,您可以使用while(true)语法创建一个无限循环。

通常,您不希望 JavaScript 一遍又一遍地执行完全相同的操作。在几乎每个循环中,一个或多个变量会随着循环的每次迭代而改变。由于变量会改变,执行statement的操作可能每次循环时都不同。此外,如果涉及到expression中的变化变量,那么表达式的值可能每次循环时都不同。这很重要;否则,一开始为真值的表达式永远不会改变,循环永远不会结束!以下是一个打印从 0 到 9 的数字的while循环示例:

let count = 0;
while(count < 10) {
    console.log(count);
    count++;
}

正如你所看到的,变量count从 0 开始,并且在循环体运行每次后递增。一旦循环执行了 10 次,表达式变为false(即变量count不再小于 10),while语句结束,解释器可以继续执行程序中的下一条语句。许多循环都有像count这样的计数变量。变量名ijk通常用作循环计数器,但如果使用更具描述性的名称可以使代码更易于理解。

5.4.2 do/while

do/while循环类似于while循环,不同之处在于循环表达式在循环底部测试而不是在顶部测试。这意味着循环体始终至少执行一次。语法是:

do
    *`statement`*
while (*`expression`*);

do/while循环比其while表亲更少使用——实际上,很少有确定要执行至少一次循环的情况。以下是do/while循环的示例:

function printArray(a) {
    let len = a.length, i = 0;
    if (len === 0) {
        console.log("Empty Array");
    } else {
        do {
            console.log(a[i]);
        } while(++i < len);
    }
}

do/while循环和普通的while循环之间有一些语法上的差异。首先,do循环需要do关键字(标记循环开始)和while关键字(标记结束并引入循环条件)。此外,do循环必须始终以分号结尾。如果循环体用大括号括起来,则while循环不需要分号。

5.4.3 for

for语句提供了一个循环结构,通常比while语句更方便。for语句简化了遵循常见模式的循环。大多数循环都有某种计数变量。该变量在循环开始之前初始化,并在每次循环迭代之前进行测试。最后,在循环体结束之前,计数变量会递增或以其他方式更新,然后再次测试该变量。在这种循环中,初始化、测试和更新是循环变量的三个关键操作。for语句将这三个操作编码为表达式,并将这些表达式作为循环语法的显式部分:

for(*`initialize`* ; *`test`* ; *`increment`*)
    *`statement`*

initializetestincrement是三个(用分号分隔的)表达式,负责初始化、测试和递增循环变量。将它们都放在循环的第一行中可以轻松理解for循环正在做什么,并防止遗漏初始化或递增循环变量等错误。

解释for循环如何工作的最简单方法是展示等效的while循环:²

*`initialize`*;
while(*`test`*) {
    *`statement`*
    *`increment`*;
}

换句话说,initialize表达式在循环开始之前只计算一次。为了有用,此表达式必须具有副作用(通常是赋值)。JavaScript 还允许initialize是一个变量声明语句,这样您可以同时声明和初始化循环计数器。test表达式在每次迭代之前进行评估,并控制循环体是否执行。如果test评估为真值,则执行循环体的statement。最后,评估increment表达式。同样,这必须是具有副作用的表达式才能有效。通常,它是一个赋值表达式,或者使用++--运算符。

我们可以使用以下for循环打印从 0 到 9 的数字。将其与前一节中显示的等效while循环进行对比:

for(let count = 0; count < 10; count++) {
    console.log(count);
}

当然,循环可能比这个简单示例复杂得多,有时多个变量在循环的每次迭代中都会发生变化。这种情况是 JavaScript 中唯一常用逗号运算符的地方;它提供了一种将多个初始化和递增表达式组合成适合在for循环中使用的单个表达式的方法:

let i, j, sum = 0;
for(i = 0, j = 10 ; i < 10 ; i++, j--) {
    sum += i * j;
}

到目前为止,我们所有的循环示例中,循环变量都是数字。这是很常见的,但并非必须的。以下代码使用for循环遍历一个链表数据结构并返回列表中的最后一个对象(即,第一个没有next属性的对象):

function tail(o) {                          // Return the tail of linked list o
    for(; o.next; o = o.next) /* empty */ ; // Traverse while o.next is truthy
    return o;
}

注意,这段代码没有初始化表达式。for循环中的三个表达式中的任何一个都可以省略,但两个分号是必需的。如果省略测试表达式,则循环将永远重复,for(;;)就像while(true)一样是写无限循环的另一种方式。

5.4.4 for/of

ES6 定义了一种新的循环语句:for/of。这种新类型的循环使用for关键字,但是与常规的for循环完全不同。(它也与我们将在§5.4.5 中描述的旧的for/in循环完全不同。)

for/of循环适用于可迭代对象。我们将在第十二章中详细解释对象何时被视为可迭代,但在本章中,只需知道数组、字符串、集合和映射是可迭代的:它们代表一个序列或一组元素,您可以使用for/of循环进行循环或迭代。

例如,这里是我们如何使用for/of循环遍历一个数字数组的元素并计算它们的总和:

let data = [1, 2, 3, 4, 5, 6, 7, 8, 9], sum = 0;
for(let element of data) {
    sum += element;
}
sum       // => 45

表面上,语法看起来像是常规的for循环:for关键字后面跟着包含有关循环应该执行的详细信息的括号。在这种情况下,括号包含一个变量声明(或者对于已经声明的变量,只是变量的名称),后面跟着of关键字和一个求值为可迭代对象的表达式,就像这种情况下的data数组一样。与所有循环一样,for/of循环的主体跟在括号后面,通常在花括号内。

在刚才显示的代码中,循环体会针对data数组的每个元素运行一次。在执行循环体之前,数组的下一个元素会被分配给元素变量。数组元素按顺序从第一个到最后一个进行迭代。

数组是“实时”迭代的——在迭代过程中进行的更改可能会影响迭代的结果。如果我们在循环体内添加data.push(sum);这行代码,那么我们将创建一个无限循环,因为迭代永远无法到达数组的最后一个元素。

使用对象进行for/of循环

对象默认情况下不可迭代。尝试在常规对象上使用for/of会在运行时引发 TypeError:

let o = { x: 1, y: 2, z: 3 };
for(let element of o) { // Throws TypeError because o is not iterable
    console.log(element);
}

如果要遍历对象的属性,可以使用for/in循环(在§5.4.5 中介绍),或者使用for/ofObject.keys()方法:

let o = { x: 1, y: 2, z: 3 };
let keys = "";
for(let k of Object.keys(o)) {
    keys += k;
}
keys  // => "xyz"

这是因为Object.keys()返回一个对象的属性名称数组,数组可以使用for/of进行迭代。还要注意,与上面的数组示例不同,对象的键的这种迭代不是实时的——在循环体中对对象o进行的更改不会影响迭代。如果您不关心对象的键,也可以像这样迭代它们对应的值:

let sum = 0;
for(let v of Object.values(o)) {
    sum += v;
}
sum // => 6

如果您对对象属性的键和值都感兴趣,可以使用for/ofObject.entries()和解构赋值:

let pairs = "";
for(let [k, v] of Object.entries(o)) {
    pairs += k + v;
}
pairs  // => "x1y2z3"

Object.entries()返回一个数组,其中每个内部数组表示对象的一个属性的键/值对。在这个代码示例中,我们使用解构赋值来将这些内部数组解包成两个单独的变量。

使用字符串进行for/of循环

在 ES6 中,字符串是逐个字符可迭代的:

let frequency = {};
for(let letter of "mississippi") {
    if (frequency[letter]) {
        frequency[letter]++;
    } else {
        frequency[letter] = 1;
    }
}
frequency   // => {m: 1, i: 4, s: 4, p: 2}

请注意,字符串是按 Unicode 代码点迭代的,而不是按 UTF-16 字符。字符串“I ❤

”的.length为 5(因为两个表情符号字符分别需要两个 UTF-16 字符来表示)。但如果您使用for/of迭代该字符串,循环体将运行三次,分别为每个代码点“I”、“❤”和“

”。

使用 Set 和 Map 进行 for/of

内置的 ES6 Set 和 Map 类是可迭代的。当您使用 for/of 迭代 Set 时,循环体会为集合的每个元素运行一次。您可以使用以下代码打印文本字符串中的唯一单词:

let text = "Na na na na na na na na Batman!";
let wordSet = new Set(text.split(" "));
let unique = [];
for(let word of wordSet) {
    unique.push(word);
}
unique // => ["Na", "na", "Batman!"]

Map 是一个有趣的情况,因为 Map 对象的迭代器不会迭代 Map 键或 Map 值,而是键/值对。在每次迭代中,迭代器返回一个数组,其第一个元素是键,第二个元素是相应的值。给定一个 Map m,您可以像这样迭代并解构其键/值对:

let m = new Map([[1, "one"]]);
for(let [key, value] of m) {
    key    // => 1
    value  // => "one"
}

使用 for/await 进行异步迭代

ES2018 引入了一种新类型的迭代器,称为异步迭代器,以及与之配套的 for/of 循环的变体,称为 for/await 循环,可与异步迭代器一起使用。

您需要阅读第十二章和第十三章才能理解 for/await 循环,但以下是代码示例:

// Read chunks from an asynchronously iterable stream and print them out
async function printStream(stream) {
    for await (let chunk of stream) {
        console.log(chunk);
    }
}

5.4.5 for/in

for/in 循环看起来很像 for/of 循环,只是将 of 关键字更改为 in。在 of 之后,for/of 循环需要一个可迭代对象,而 for/in 循环在 in 之后可以使用任何对象。for/of 循环是 ES6 中的新功能,但 for/in 从 JavaScript 最初就存在(这就是为什么它具有更自然的语法)。

for/in 语句循环遍历指定对象的属性名称。语法如下:

for (*`variable`* in *`object`*)
    *`statement`*

variable 通常命名一个变量,但它也可以是一个变量声明或任何适合作为赋值表达式左侧的内容。object 是一个求值为对象的表达式。通常情况下,statement 是作为循环主体的语句或语句块。

您可能会像这样使用 for/in 循环:

for(let p in o) {      // Assign property names of o to variable p
    console.log(o[p]); // Print the value of each property
}

要执行 for/in 语句,JavaScript 解释器首先评估 object 表达式。如果它评估为 nullundefined,解释器将跳过循环并继续执行下一条语句。解释器现在会为对象的每个可枚举属性执行循环体。然而,在每次迭代之前,解释器会评估 variable 表达式并将属性的名称(一个字符串值)赋给它。

请注意,在 for/in 循环中的 variable 可以是任意表达式,只要它评估为适合赋值左侧的内容。这个表达式在每次循环时都会被评估,这意味着它可能每次评估的结果都不同。例如,您可以使用以下代码将所有对象属性的名称复制到数组中:

let o = { x: 1, y: 2, z: 3 };
let a = [], i = 0;
for(a[i++] in o) /* empty */;

JavaScript 数组只是一种特殊类型的对象,数组索引是可以用 for/in 循环枚举的对象属性。例如,以下代码后面加上这行代码,将枚举数组索引 0、1 和 2:

for(let i in a) console.log(i);

我发现在我的代码中常见的错误来源是意外使用数组时使用 for/in 而不是 for/of。在处理数组时,您几乎总是希望使用 for/of 而不是 for/in

for/in 循环实际上并不枚举对象的所有属性。它不会枚举名称为符号的属性。对于名称为字符串的属性,它只循环遍历可枚举属性(参见§14.1)。核心 JavaScript 定义的各种内置方法都不可枚举。例如,所有对象都有一个 toString() 方法,但 for/in 循环不会枚举这个 toString 属性。除了内置方法,许多内置对象的其他属性也是不可枚举的。默认情况下,您代码定义的所有属性和方法都是可枚举的(您可以使用§14.1 中解释的技术使它们变为不可枚举)。

可枚举的继承属性(参见§6.3.2)也会被for/in循环枚举。这意味着如果您使用for/in循环,并且还使用定义了所有对象都继承的属性的代码,那么您的循环可能不会按您的预期方式运行。因此,许多程序员更喜欢使用Object.keys()for/of循环而不是for/in循环。

如果for/in循环的主体删除尚未枚举的属性,则该属性将不会被枚举。如果循环的主体在对象上定义了新属性,则这些属性可能会被枚举,也可能不会被枚举。有关for/in枚举对象属性的顺序的更多信息,请参见§6.6.1。

5.5 跳转

另一类 JavaScript 语句是跳转语句。顾名思义,这些语句会导致 JavaScript 解释器跳转到源代码中的新位置。break语句使解释器跳转到循环或其他语句的末尾。continue使解释器跳过循环体的其余部分,并跳回到循环的顶部开始新的迭代。JavaScript 允许对语句进行命名,或标记breakcontinue可以标识目标循环或其他语句标签。

return语句使解释器从函数调用跳回到调用它的代码,并提供调用的值。throw语句是一种临时从生成器函数返回的方式。throw语句引发异常,并设计用于与try/catch/finally语句一起工作,后者建立了一个异常处理代码块。这是一种复杂的跳转语句:当抛出异常时,解释器会跳转到最近的封闭异常处理程序,该处理程序可能在同一函数中或在调用函数的调用堆栈中。

关于这些跳转语句的详细信息在接下来的章节中。

5.5.1 标记语句

任何语句都可以通过在其前面加上标识符和冒号来标记

*`identifier`*: *`statement`*

通过给语句加上标签,您为其赋予一个名称,以便在程序的其他地方引用它。您可以为任何语句加上标签,尽管只有为具有主体的语句加上标签才有用,例如循环和条件语句。通过给循环命名,您可以在循环体内使用breakcontinue语句来退出循环或直接跳转到循环的顶部开始下一次迭代。breakcontinue是唯一使用语句标签的 JavaScript 语句;它们在以下子节中介绍。这里是一个带有标签的while循环和使用标签的continue语句的示例。

mainloop: while(token !== null) {
    // Code omitted...
    continue mainloop;  // Jump to the next iteration of the named loop
    // More code omitted...
}

用于标记语句的标识符可以是任何合法的 JavaScript 标识符,不能是保留字。标签的命名空间与变量和函数的命名空间不同,因此您可以将相同的标识符用作语句标签和变量或函数名称。语句标签仅在其适用的语句内部定义(当然也包括其子语句)。语句不能具有包含它的语句相同的标签,但是只要一个语句不嵌套在另一个语句内,两个语句可以具有相同的标签。标记的语句本身也可以被标记。实际上,这意味着任何语句可以具有多个标签。

JavaScript 权威指南第七版(GPT 重译)(二)(4)https://developer.aliyun.com/article/1485295

相关文章
|
12天前
|
存储 缓存 自然语言处理
JavaScript 权威指南第七版(GPT 重译)(三)(4)
JavaScript 权威指南第七版(GPT 重译)(三)
32 3
|
12天前
|
JSON 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(五)(2)
JavaScript 权威指南第七版(GPT 重译)(五)
36 5
|
自然语言处理 JavaScript 前端开发
JavaScript 权威指南第七版(GPT 重译)(一)(2)
JavaScript 权威指南第七版(GPT 重译)(一)
30 2
|
12天前
|
存储 JSON JavaScript
JavaScript 权威指南第七版(GPT 重译)(三)(1)
JavaScript 权威指南第七版(GPT 重译)(三)
84 2
|
12天前
|
Web App开发 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(四)(1)
JavaScript 权威指南第七版(GPT 重译)(四)
35 2
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(二)(2)
JavaScript 权威指南第七版(GPT 重译)(二)
35 2
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(3)
JavaScript 权威指南第七版(GPT 重译)(七)
32 0
|
前端开发 JavaScript Unix
JavaScript 权威指南第七版(GPT 重译)(七)(2)
JavaScript 权威指南第七版(GPT 重译)(七)
42 0
|
前端开发 JavaScript 安全
JavaScript 权威指南第七版(GPT 重译)(七)(4)
JavaScript 权威指南第七版(GPT 重译)(七)
24 0
|
12天前
|
前端开发 JavaScript API
JavaScript 权威指南第七版(GPT 重译)(六)(3)
JavaScript 权威指南第七版(GPT 重译)(六)
55 4