原文:
zh.annas-archive.org/md5/caa9de298acf1565f89d991a81f1f93a译者:飞龙
前言
JavaScript 是世界上使用最广泛的编程语言。它拥有众多的库和模块,以及令人眼花缭乱的必须了解的主题。选择一个起点可能很困难。这本简洁实用的指南将让您在极短的时间内掌握所需技能。
本书面向的对象
这本书是为那些希望加强他们的核心 JavaScript 概念并在构建全栈应用程序中实现它们的 JavaScript 开发者而写的。
本书涵盖的内容
第一章,探索 JavaScript 的核心概念,是您发现如何在 JavaScript 中使用变量、条件和循环的地方。
第二章,探索 JavaScript 的高级概念,是您学习如何在 JavaScript 中使用面向对象编程的地方。
第三章,Vue.js 入门,是您学习 Vue.js 的基础,包括组件和指令的地方。
第四章,Vue.js 的高级概念,是您深入探索 Vue.js,包括组件间通信和视觉效果的地方。
第五章,使用 Vue.js 管理列表,是您学习如何使用 Vue.js 构建一个完整项目的地方。
第六章,创建和使用 Node.js 模块,是您学习使用模块进行 Node.js 编程基础的地方。
第七章,使用 Express 与 Node.js,是您探索用于构建 Node.js 应用程序的主要库的地方。
第八章,使用 MongoDB 与 Node.js,是您学习如何使用 Mongoose 模块在 Node.js 中使用 MongoDB 数据库的地方。
第九章,整合 Vue.js 与 Node.js,是您学习如何构建一个整合 Vue.js 和 Node.js 的完整项目的地方。
要充分利用这本书
对 HTML 和 CSS 的先验知识是这本书的必备条件。
如果您正在使用这本书的数字版,我们建议您亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub(github.com/PacktPublishing/JavaScript-from-Frontend-to-Backend)下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富的书籍和视频目录的代码包,可在github.com/PacktPublishing/找到。去看看吧!
下载彩色图片
我们还提供了一份包含本书中使用的截图和图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/xdibe
使用的约定
本书使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“所以 { lastname: "Clinton" } 也可以通过将 lastname 属性用单引号或双引号包围来写成 { "lastname": "Clinton" }。”
代码块设置如下:
var p = { lastname : "Clinton", firstname : "Bill" };
console.log("The person is", p);
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
class Person {
firstname;
lastname;
age;
}
var p = new Person;
console.log(p);
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“这种写作格式也称为JavaScript 对象表示法(JSON)格式。”
小贴士或重要注意事项
看起来像这样。
联系我们
我们读者的反馈总是受欢迎的。
一般反馈:如果您对本书的任何方面有疑问,请通过 customercare@packtpub.com 给我们发邮件,并在邮件主题中提及书名。
勘误:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上发现我们作品的任何形式的非法副本,我们将不胜感激,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packt.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为本书做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了 从前端到后端的 JavaScript,我们很乐意听听您的想法!请点击此处直接访问此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区都非常重要,它将帮助我们确保我们提供高质量的内容。
第一部分:JavaScript 语法
本部分解释了您在客户端或服务器上使用 JavaScript 所需了解的基本知识。它解释了 JavaScript 中的语法和主要数据类型。
本节包含以下章节:
-
第一章,探索 JavaScript 的核心概念
-
第二章,探索 JavaScript 的高级概念
第一章:第一章 探索 JavaScript 的核心概念
JavaScript 语言是在 1990 年代中期创建的,用于在互联网浏览器中执行,以便使网站更加流畅。最初,它被用来控制输入表单中的内容。例如,它被用来做以下事情:
-
允许在字段中输入数字字符——并且只有数字字符。在这种情况下,其他字符,例如字母,必须被拒绝。这使得由于浏览器中包含的 JavaScript 语言,可以不验证表单的输入并避免将数据发送到服务器,在这种情况下会表明输入错误。
-
在将表单字段发送到服务器之前,检查表单的所有必填字段是否都已填写。
这两个示例(以及其他许多示例)表明,在将用户输入的数据发送到服务器之前,检查数据的有效性是可取的。这避免了在输入的数据不正确的情况下,从浏览器到服务器的数据传输。对于更复杂的检查,例如检查两个人是否具有相同的标识符,这可以在服务器上继续进行,因为服务器可以访问所有现有的标识符。
因此,JavaScript 的目标是在其初期,让浏览器尽可能多地检查,然后将输入的信息传输到服务器进行处理。
为了这个目的,创建了一种内部浏览器语言:JavaScript 语言,其名称包含当时一个非常流行的词——“Java”(尽管 Java 和 JavaScript 这两种语言之间没有任何关系)。
多年来,开发者们有了将之与服务器端关联起来的想法,以便在客户端和服务器端使用相同的语言。这允许创建 Node.js 服务器,它今天被广泛使用。
不论是客户端还是服务器端,JavaScript 语言使用一种基本的语法,允许你编写自己的程序。这是我们将在本章中要探讨的。
在本章中,我们将涵盖以下主题:
-
JavaScript 中使用的变量类型
-
运行 JavaScript 程序
-
在 JavaScript 中声明变量
-
编写条件测试的条件
-
创建处理循环
-
使用函数
技术要求
要在 JavaScript 中开发,并在此书中编写和运行程序,你需要以下内容:
-
用于计算机程序的文本编辑器,例如 Notepad++、Sublime Text、EditPlus 或 Visual Studio。
-
一个互联网浏览器,例如 Chrome、Firefox、Safari 或 Edge。
-
一个 PHP 服务器,例如 XAMPP 或 WampServer。PHP 服务器将用于在 HTML 页面上执行包含
import语句的 JavaScript 程序,因为这些import语句只能在 HTTP 服务器上工作。 -
Node.js 服务器:Node.js 服务器将通过 Node.js 安装创建。我们还将安装并使用 MongoDB 数据库,以将 Node.js 服务器与数据库关联。
-
您可以在 GitHub 上找到本章的代码文件:
github.com/PacktPublishing/JavaScript-from-Frontend-to-Backend/blob/main/Chapter%201.zip。
让我们现在开始探索 JavaScript,通过研究它为我们提供的不同类型的变量。
JavaScript 中使用的变量类型
与任何语言一样,JavaScript 允许你创建用于操作数据的变量。JavaScript 是一种非常简单的语言,因此,例如,数据类型非常基础。因此,我们将以下内容作为主要数据类型:
-
数值
-
布尔值
-
字符串
-
数组
-
对象
让我们快速浏览这些不同类型的数据。
数值
数值可以是正数或负数,甚至可以是小数形式(例如,0,-10,10.45)。所有称为实数的数学数都包含数值或数据点。
布尔值
当然,这是两种布尔值——true 或 false,这在大多数语言中都可以找到。这些值用于表达条件:如果条件为真,则执行特定过程,否则执行另一个过程。因此,条件的结果是 true 或 false 值,分别用两个值 true 和 false 表示。
我们将在本章后面的“编写条件”部分中看到如何表达条件。
字符串
字符串指的是像 "a","abc" 或 "Hello, how are you?" 这样的值。空字符串将表示为 ""(连续的引号,里面没有任何内容)。注意,你可以使用双引号(")或单引号(')。因此,字符串 "abc" 也可以写成 'abc'(使用单引号)。
数组
数组,如 [10, "abc", -36],可以包含任何类型的值,例如这里我们既有数值也有字符串。空数组将表示为 [],这意味着它不包含任何值。
存储在数组中的值通过索引访问,索引从 0(用于访问数组中放置的第一个元素)到数组的长度减 1(用于访问数组的最后一个元素)。因此,如果数组 [10, "abc", -36] 通过变量 tab 表示,例如,以下情况会发生:
-
tab[0]将允许访问数组的第一个元素:10。 -
tab[1]将允许访问数组的第二个元素:"abc"。 -
tab[2]将允许访问数组的第三个和最后一个元素:-36。注意
注意,你可以在数组为空的情况下向数组中添加元素。因此,如果我们访问前面数组
tab的索引 3,我们可以写tab[3] = "def"。因此,数组tab现在将是[10, "abc", -36, "def"]。
对象
对象类似于数组。它们用于存储任意信息,例如,值43,"Clinton",和"Bill"。但与使用索引的数组不同,你必须指定一个名称来访问这些值中的每一个。这个名称被称为键,因此它允许访问它所代表的值。
假设之前的值43是某人的年龄,而"Clinton"是他们的姓,"Bill"是他们的名。那么我们将对象写成以下形式:{ age: 43, lastname: "Clinton", firstname: "Bill" }。对象的定义是通过花括号完成的,内部是key: value形式的键值对,由逗号分隔。这种书写格式也称为JavaScript 对象表示法(JSON)格式。
因此,如果之前的对象与变量person相关联,我们可以通过编写person["age"](在这里将是43)来访问他们的年龄,但也可以编写person.age,这也会是43。同样,我们也可以编写person.lastname或person["lastname"]以及person.firstname或person["firstname"]来分别访问该人的姓和名。
键也被称为对象的属性。因此,age键也被称为age属性。我们可以为键选择任何名称;你只需指出键,然后使用这个名称。所以,如果你在person对象中将age指定为属性,你必须使用person.age或person["age"]中的术语;否则它将不起作用。
注意,如果你写person[age]而不是person["age"],JavaScript 会将age视为一个具有先前定义值的变量,而在这里它不是,因此在这种情况下无法工作。你必须将age变量设置为具有值"age"才能使其工作。
数组的元素按照它们的索引顺序排列(从 0 开始,然后是 1,依此类推),而包含在对象中的元素按照每个元素指定的键顺序排列。尽管lastname键在person对象中列在firstname键之前,但这并不区分对象{ age: 43, lastname: "Clinton", firstname: "Bill" }和对象{ firstname: "Bill", lastname: "Clinton", age: 43 },因为键写入对象的顺序是不相关的。
最后,存在空对象,例如那些不包含键(因此没有值)的对象。我们以{ }的形式写一个空对象,表示里面没有任何内容。然后我们可以向一个对象添加一个或多个键,即使它最初是空的。
现在我们已经看到了 JavaScript 中使用的的主要变量类型,让我们看看如何使用它们在我们的程序中定义变量。
运行 JavaScript 程序
JavaScript 是一种可以在浏览器(Edge、Chrome、Firefox、Safari 等等)或安装了 Node.js 的服务器上执行的语言。让我们看看如何为这两种配置编写 JavaScript 程序。
在浏览器中运行 JavaScript 程序
要在浏览器中运行 JavaScript 程序,必须将 JavaScript 代码插入到 HTML 文件中。然后,该 HTML 文件将在浏览器中显示,这将导致文件中包含的 JavaScript 代码执行。
JavaScript 代码可以在 HTML 文件中以两种不同的方式指定:
-
第一种方式是直接在 HTML 文件中的
<script>和</script>标签之间编写。<script>标签表示 JavaScript 代码的开始,而</script>标签表示其结束。在这两个标签之间编写的任何内容都被认为是 JavaScript 代码。 -
第二种方式是将 JavaScript 代码写入外部文件,然后将其包含在 HTML 文件中。外部文件通过在 HTML 文件中包含一个
<script>标签来包含,其中src属性指示的值是要包含在 HTML 页面中的 JavaScript 文件的名称。
让我们来看看这两种在浏览器中运行的 JavaScript 代码的编写方式。
在 标签之间编写 JavaScript 代码
使用 .html 扩展名的文件;例如,index.html 文件。这是一个传统的 HTML 文件,我们在其中插入了 <script> 和 </script> 标签,如下面的代码片段所示:
index.html 文件
<html>
<head>
<meta charset="utf-8" />
<script>
alert("This is a warning message displayed by
JavaScript");
</script>
</head>
<body>
</body>
</html>
我们在 HTML 页面的 <head> 部分插入了 <script> 标签(及其结束标签 </script>)。<meta> 标签用于指示要使用的字符编码。在前面的代码中,我们使用了 utf-8,以便正确显示带重音的字符。
在此处插入的 JavaScript 代码非常基础。我们使用了 alert() 函数,该函数会在浏览器屏幕上显示一个对话框,显示函数第一个参数中指示的消息文本。
要运行此 HTML 文件,只需将其(通过拖放)从文件管理器移动到任何浏览器;例如,Firefox。然后会显示以下屏幕:
图 1.1 – 在浏览器窗口中显示消息
在 <script> 标签中存在的 JavaScript 代码在 HTML 页面加载时运行。因此,alert() 函数中指示的消息会被显示出来。点击 确定 按钮验证显示的消息并继续执行 JavaScript 代码。正如我们所看到的,程序中没有任何其他内容;程序立即通过在屏幕上显示空白页面结束(因为没有将 HTML 代码插入到页面中)。
将 JavaScript 代码写入外部文件
而不是直接将 JavaScript 代码集成到 HTML 文件中,我们可以将其放在一个外部文件中,然后通过在 <script> 标签的 src 属性中指定其名称来将此文件插入到我们的 HTML 文件中。
让我们首先编写将包含 JavaScript 代码的文件。这个文件具有 .js 文件扩展名,将被命名为 codejs.js,例如,其代码如下:
codejs.js 文件(位于 index.html 同一目录下)
alert("This is a warning message displayed by JavaScript");
codejs.js 文件包含我们之前在 <script> 和 </script> 标签之间插入的 JavaScript 代码。
index.html 文件被修改以包含 codejs.js 文件,使用 <script> 标签的 src 属性如下:
index.html 文件
<html>
<head>
<meta charset="utf-8" />
<script src="img/codejs.js"></script>
</head>
<body>
</body>
</html>
注意
注意 <script> 和 </script> 标签的使用。它们是连续的(也就是说,它们之间没有空格或换行符),这对于代码的正常运行是必要的。
在我们接下来的示例中,我们将主要使用直接在 HTML 文件中插入 JavaScript 代码,但使用外部文件会产生相同的结果。
现在让我们解释另一种显示消息的方法,这种方法不会像之前使用 alert(message) 函数那样阻塞程序。
使用 console.log() 方法代替 alert() 函数
之前使用的 alert() 函数在 HTML 页面上显示一个窗口,JavaScript 程序会挂起等待用户在窗口中点击 确定 按钮。因此,该函数需要用户的干预才能继续程序的执行。
一种替代方法使得可以在不阻塞程序执行的情况下使用显示。这是在控制台中,使用 console.log() 方法。
注意
console.log() 写法意味着我们使用与 console 对象关联的 log() 方法。这将在下一章中详细解释。
让我们再次编写程序,这次使用 console.log() 方法而不是 alert() 函数。index.html 文件将按如下方式修改:
使用 console.log() 方法的 index.html 文件
<html>
<head>
<meta charset="utf-8" />
<script>
// display a message in the console
console.log("This is a warning message displayed by
JavaScript");
</script>
</head>
<body>
</body>
</html>
注意
在 JavaScript 程序中使用注释需要将 // 放在需要注释的内容之前(在同一行上)。您也可以通过在开头和结尾使用 /* 和 */ 来注释多行。
通过按键盘上的 F5 键来运行此程序以刷新窗口。会出现一个空白屏幕,没有任何消息。
事实上,消息只会在控制台中显示。控制台只有在您按下 F12 键时才会可见(可以通过再次按下 F12 来移除)。
注意
您可以访问网站 balsamiq.com/support/faqs/browserconsole/,该网站解释了在 F12 键无效的情况下如何显示控制台。
以下是在控制台显示的内容:
![图 1.2 – 控制台中显示的消息
![图 1.2 – 控制台中显示的消息
图 1.2 – 控制台中显示的消息
消息显示在浏览器窗口的下半部分。
现在我们已经学会了如何在浏览器中运行 JavaScript 程序,接下来让我们学习如何在 Node.js 服务器上运行 JavaScript 程序。
在 Node.js 服务器上运行 JavaScript 程序
要在 Node.js 服务器上运行 JavaScript 程序,你必须首先安装 Node.js 服务器。要安装,只需访问 nodejs.org/ 并下载和安装服务器。请注意,如果你使用 macOS,Node.js 已经安装了。
我们可以通过打开一个壳并输入命令 node -h 来验证 Node.js 的正确安装。如果命令帮助显示如下,则表示 Node.js 已正确安装:
![Figure 1.3 – node -h 命令显示帮助信息
![img/Figure_1.3_B17416.jpg]
图 1.3 – 显示帮助信息的 node -h 命令
一旦安装了 Node.js,它就可以运行你想要的任何 JavaScript 程序。你所要做的就是创建一个包含 JavaScript 代码的文件,例如,testnode.js。该文件的内容将由服务器使用 node testnode.js 命令执行。
这里是一个非常简单的 JavaScript 文件示例,它可以由 Node.js 执行:它在服务器控制台中显示一条消息。这里的“服务器控制台”代表命令解释器,你在其中输入命令以执行 testnode.js 文件:
testnode.js 文件
console.log("This is a warning message displayed by JavaScript");
让我们在前面的终端窗口中输入命令 node testnode.js。
![Figure 1.4 – 运行 Node.js 程序
![img/Figure_1.4_B17416.jpg]
图 1.4 – 运行 Node.js 程序
我们看到消息直接显示在命令解释器中。
在前面的例子中,我们编写的 JavaScript 代码既可以在客户端(浏览器)运行,也可以在服务器端运行。可以提出的问题是:相同的代码是否可以在客户端和服务器端以完全相同的方式运行?
为浏览器和服务器编写的 JavaScript 代码之间的差异
虽然这两段代码很相似,但我们不能说它们是相同的,因为在两种情况下要处理的问题不同。实际上,在客户端,我们主要会想用 JavaScript 管理用户界面,而在服务器端,我们更想管理文件或数据库。因此,在这两种情况下要使用的库将不会相同。
另一方面,我们在两种情况下都找到了相同的基本语言,那就是我们将要描述的 JavaScript 语言。
在 JavaScript 中声明变量
在“JavaScript 中使用的变量类型”部分之前描述的变量类型,如我们所知,包括数值、布尔值、字符字符串、数组和对象。
JavaScript 是一种弱类型语言,这意味着你可以在任何时候更改变量的类型。例如,数值变量可以被转换成字符字符串,甚至可以变成数组。
当然,在程序中做出这样的自愿更改是不明智的,并且为了理解,保持变量的类型在整个程序中是谨慎的。然而,重要的是要知道 JavaScript 允许更改变量类型。一种名为 TypeScript 的 JavaScript 变体通过防止这些类型更改来提供更多的安全性。
现在,让我们学习如何定义变量。我们将使用以下关键字之一:const、var 或 let。
使用 const 关键字
const 关键字用于定义一个值将保持不变的变量。任何后续尝试更改值都将产生错误。
让我们定义一个常量变量 c1,其值为 12。尝试修改其值,给它赋予一个新的值:控制台将显示错误:
注意
将我们定义一个常量变量称为语言上的滥用。我们更应该说我们在定义一个常量值。
定义常量值(index.html 文件)
<html>
<head>
<meta charset="utf-8" />
<script>
const c1 = 12;
console.log(c1);
c1 = 13; // attempt to modify the value of a
// constant: error
console.log(c1); // no display because an error
// occurred above
</script>
</head>
<body>
</body>
</html>
在实现前面的代码后,我们还将看到控制台(如果控制台不可见,可以通过按 F12 键显示)中显示的错误如下:
Figure 1.5 – 修改常量值时的错误
从前面的图中我们可以看到,常量 c1 的第一次显示值为 const,关键字不应被修改。
使用 var 关键字
定义变量(其值可以修改)的另一种方式是使用 var 关键字。让我们通过以下代码示例来看看如何使用:
几个变量的定义
<html>
<head>
<meta charset="utf-8" />
<script>
var a = 12;
var b = 56;
var c = a + b;
var s1 = "My name is ";
var firstname = "Bill";
console.log("a + b = " + a + b);
console.log("c = " + c);
console.log(s1 + firstname);
</script>
</head>
<body>
</body>
</html>
我们通过在变量前加上关键字 var 并赋予它们默认值来定义变量 a、b、s1 和 firstname。变量 c 对应于变量 a 和 b 的和。
注意
变量的名称由字母数字字符组成,但必须以字母字符开头。在编写变量名称时,大小写很重要(变量的名称是区分大小写的)。因此,变量 a 与变量 A 不同。
前一个程序的结果在浏览器控制台中显示(如果不可见,必须通过按 F12 键显示):
Figure 1.6 – 使用 var 关键字
在前面的图中,我们可以看到一个可能看起来令人惊讶的结果。确实,a + b 的直接计算第一次显示为 1256,然后第二次显示为 68。
事实上,当我们写 console.log("a + b = " + a + b); 时,我们开始通过写入 "a + b = " 来显示字符的事实意味着 JavaScript 将将显示的其余部分解释为字符字符串;特别是,位于同一行的 a 和 b 的值。因此,a 和 b 的值不再被解释为数值,而是作为字符字符串 12 和 56。当这些字符字符串通过 + 运算符连接时,这并不对应于加法,而是连接。
相反,变量 c 的计算不涉及字符字符串,因此这里 a + b 的结果是变量 a 和 b 的值的总和,因此 68。
注意,同样的程序可以在 Node.js 服务器上运行。为此,我们可以在 testnode.js 文件中这样编写:
testnode.js 文件
var a = 12;
var b = 56;
var c = a + b;
var s1 = "My name is ";
var firstname = "Bill";
console.log("a + b = " + a + b);
console.log("c = " + c);
console.log(s1 + firstname);
然后,我们可以使用 node testnode.js 命令执行前面的代码。在 Node.js 下显示的结果与在浏览器控制台显示的结果相似:
图 1.7 – 在 Node.js 下运行程序
我们学习了用于定义变量的 const 和 var 关键字;现在我们还需要学习如何使用 let 关键字。
使用 let 关键字
要理解 let 关键字的使用并看到它与 var 关键字的区别,我们必须在我们的程序中使用大括号。大括号用于创建程序块,在其中插入指令,特别是在条件 if 和 else 指令之后(我们将在 编写条件 部分看到)。
让我们写一个简单的 if(true) 条件,它总是 true:因此,条件后面的括号内的代码总是被执行:
包含条件的 index.html 文件
<html>
<head>
<meta charset="utf-8" />
<script>
var a = 12;
if (true) { // always executed (because always true)
var b = 56;
let c = 89;
console.log("In the brace:");
console.log("a = " + a);
console.log("b = " + b);
console.log("c = " + c);
}
console.log("After the brace:");
console.log("a = " + a);
console.log("b = " + b);
console.log("c = " + c);
</script>
</head>
<body>
</body>
</html>
在前面的代码中,我们在任何大括号之外定义了变量 a。因此,一旦定义,这个变量将在任何地方(包括和不包括大括号)都是可访问的。
变量 b 和 c 在条件之后的大括号内定义。变量 b 使用 var 定义,而变量 c 使用 let 关键字定义。两个变量之间的区别在退出大括号块时就会显现出来。确实,变量 c(由 let 定义)在其定义的大括号块外部不再被识别,而变量 b(由 var 定义)即使在块外部也是可访问的。
这可以通过在浏览器中按如下方式运行程序来检查:
图 1.8 – 由 let 定义的变量 c 在其定义的块外部不可访问
注意,同样的程序在 Node.js 服务器上也会得到类似的结果,如下面的屏幕截图所示:使用 let 定义的变量 c 在块外部变得不可知。
图 1.9 – Node.js 服务器上的相同结果
正如我们在前面的屏幕上所看到的,由 let 在块中定义的变量 c 在块外变得不可知。
如果我们不使用 var 或 let 来定义一个变量会怎样?
有可能不使用 var 或 let 关键字来定义一个变量。我们可以简单地写出变量的名称,然后写出它的值(由 = 符号分隔)。让我们通过以下示例看看这样做会怎样:
不指定 var 或 let 创建变量
a = 12;
b = 56;
console.log("a = " + a); // displays the value 12
console.log("b = " + b); // displays the value 56
在前面的例子中,变量在没有 var 或 let 前被初始化,这些变量是全局变量。一旦它们被初始化,它们就可以在程序的其他任何地方访问。当我们学习本章 使用函数 部分的函数时,这一点将变得明显。
注意
强烈建议在程序中尽可能少地使用全局变量,因为这会复杂化包含它们的程序的设计和调试。
未初始化的变量有什么价值?
前面的每个变量都是通过初始化其值(使用 = 符号,即赋值符号)来声明的。让我们看看如果我们不对变量赋值,只使用 var 或 let 声明会发生什么:
未初始化的变量声明
<html>
<head>
<meta charset="utf-8" />
<script>
var a;
let b;
console.log("a = " + a); // displays the value
// undefined
console.log("b = " + b); // displays the value
// undefined
</script>
</head>
<body>
</body>
</html>
在前面的代码中,我们定义了两个变量,a 和 b – 一个使用 var,另一个使用 let。这两个变量都没有初始值(也就是说,它们后面没有 = 符号)。
在这种情况下,对于这些未初始化的变量显示的结果是 JavaScript 中的一个值,称为 undefined。这对应于尚未有值的变量。undefined 值是 JavaScript 语言中的一个重要关键字。
注意
变量 a 和 b 没有被初始化,必须使用 var 或 let 来声明它们。实际上,你不能简单地写 a; 或 b;,因为这会导致运行时错误。
让我们在浏览器中运行前面的程序,并观察控制台显示的结果:
图 1.10 – 未初始化的变量是未定义的
注意
如果使用 Node.js 服务器端的 JavaScript,undefined 值也与未初始化的变量相关联。
我们现在知道了如何在 JavaScript 中定义变量。要创建有用的 JavaScript 程序,你必须编写一系列指令。最常用的指令之一允许你使用 if 语句编写条件测试,我们将在下一节讨论这一点。
编写条件测试的条件
JavaScript 显然允许你在程序中编写条件。条件通过 if (condition) 语句表达:
-
如果条件是
true,则执行后面的语句(或花括号中的块)。 -
如果条件为
false,则执行else关键字后面的语句(如果存在)。
编写指令的形式
我们可以使用以下形式来表示条件:
使用 if (condition) 的条件表达式形式
// condition followed by a statement
if (condition) statement; // statement executed if condition is true
// condition followed by a block
if (condition) {
// block of statements executed if condition is true
statement 1;
statement 2;
statement 3;
}
使用 if (condition) … else … 的条件表达式形式
// condition followed by a statement
if (condition) statement 1; // statement 1 executed if
// condition is true
else statement 2; // statement 2 executed if
// condition is false
// condition followed by a block
if (condition) {
// block of statements executed if condition is true
statement 1;
statement 2;
statement 3;
}
else {
// block of statements executed if condition is false
statement 5;
statement 6;
statement 7;
}
注意
如果要执行的过程包含多个指令,这些指令将组合在一起,用大括号括起来形成一个块。一个块可以只包含一个语句,即使在这个情况下,块是可选的(不需要大括号)。
让我们在 testnode.js 文件中编写以下程序,我们将使用命令解释器中的 node testnode.js 命令来执行它,如下所示:
testnode.js 文件
var a = 12;
console.log("a = " + a);
if (a == 12) console.log("a is 12");
else console.log("a is not 12");
在前面的代码中,条件以 a == 12 的形式表达。实际上,习惯上通过连续两次重复使用等号 = 来测试两个值之间的相等性(因此是 ==)。
注意
我们使用 == 表示相等,!= 表示不等,> 或 >= 检查大于或等于,< 或 <= 检查小于或等于。
在前面的代码中,由于变量 a 的值为 12,可以看到以下结果:
图 1.11 – 使用条件测试
如果我们将值 13 赋给变量 a,则语句的 else 部分将被执行:
图 1.12 – 运行测试的 else 部分
我们已经看到了如何根据条件执行代码的一部分或另一部分。现在让我们研究如何编写比之前写过的更复杂的条件。
用于编写条件的表达式
之前编写的条件是两个值之间简单相等性的测试。但有时需要编写的测试可能更复杂。目标是得到条件的最终结果,即 true 或 false,这将使系统能够决定下一步的行动。
条件用布尔形式书写,使用 OR 关键字(写作 ||)或使用 AND 关键字(写作 &&)。不同条件之间可能需要括号来表示最终条件,如下所示:
使用 “or” 表达的条件
var a = 13;
var b = 56;
console.log("a = " + a);
console.log("b = " + b);
if (a == 12 || b > 50) console.log("condition a == 12 || b > 50 is true");
else console.log("condition a == 12 || b > 50 is false");
在前面的代码中,由于变量 b 大于 50,条件为 true,如 图 1.13 所示。
注意
在 OR 条件中,只要其中一个条件为 true,最终条件就为 true。
在 AND 条件中,所有条件都必须为 true,最终条件才为 true。
图 1.13 – 使用 or 条件
默认情况下,if(condition) 中表达的条件与值 true 进行比较。有时我们可能更喜欢与值 false 进行比较。在这种情况下,只需在条件前加上符号 ! 即可,这对应于对后续条件的否定。
有时需要根据前一个测试的结果连续进行几个测试。这时,我们就有了一系列的测试,称为级联测试。
嵌套测试套件
在要执行的过程中可以链式测试。以下是一个示例:
测试嵌套
var a = 13;
var b = 56;
console.log("a = " + a);
console.log("b = " + b);
if (a == 12) console.log("condition a == 12 is true");
else {
console.log("condition a == 12 is false");
if (b > 50) console.log("condition b > 50 is true");
else console.log("condition b > 50 is false");
}
else 部分由多个语句组成,并放在由大括号包围的块中:
图 1.14 – 测试嵌套
我们学习了如何在 JavaScript 程序中编写条件。现在我们将学习如何编写处理循环,这使得在程序中只需编写一次指令成为可能。然而,这些指令可以根据需要执行多次。
创建处理循环
有时需要多次重复一个指令(或指令块)。而不是在程序中多次编写它,我们将其放入处理循环中。这些指令将根据需要重复执行多次。
JavaScript 中有两种处理循环类型:
-
使用
while()语句的循环 -
使用
for()语句的循环
让我们来看看这两种循环类型。
使用 while() 循环
while(condition) 指令允许你重复执行后面的指令(或指令块)。只要条件为 true,就会执行该语句(或块)。当条件变为 false 时停止执行。
使用这个 while() 语句,让我们显示从 0 到 5 的数字:
显示从 0 到 5 的数字
var i = 0;
while (i <= 5) {
console.log("i = " + i);
i++;
}
前面的 console.log() 指令在程序中只写一次,但由于它被插入到循环(while() 指令)中,所以当条件为 true 时,它将被重复执行多次。
变量 i 允许你在循环中管理条件。每次通过循环时,变量 i 都会增加 1(通过 i++),并且当值超过 5 时我们停止:
图 1.15 – 显示数字从 0 到 5
我们可以验证这个程序在客户端(即网页浏览器)中以类似的方式工作,如下所示:
在浏览器控制台中显示数字 0–5
<html>
<head>
<meta charset="utf-8" />
<script>
var i = 0;
while (i <= 5) {
console.log("i = " + i);
i++;
}
</script>
</head>
<body>
</body>
</html>
结果在浏览器控制台中以类似的方式显示:
图 1.16 – 在浏览器控制台中显示从 0 到 5 的数字
使用 for() 循环
另一种广泛使用的循环形式是使用 for() 语句的循环。它通过减少要编写的指令数量来简化前面循环的编写。
让我们用 for() 语句而不是 while() 语句编写与之前相同的程序来显示从 0 到 5 的数字:
for (var i=0; i <= 5; i++) console.log("i = " + i);
如前所述的代码所示,一行代码可以替代几行代码。
for() 语句有三个部分,由 ; 分隔:
-
第一个对应于初始化指令。在这里,它是变量
i的声明,初始化为0(这是循环的开始)。 -
第二个对应于条件:只要这个条件是
true,就会执行语句(或随后的语句块)。在这里,条件对应于变量i没有超过最终值5。 -
第三个对应于每次循环迭代后执行的指令。在这里,我们通过 1 增加变量
i。这确保了在某个时刻,条件将变为false,以便退出循环。
让我们验证它是否与 while() 语句完全相同:
图 1.17 – 使用 for()语句的循环
在本节中,我们学习了如何使用 while() 和 for() 语句编写将被多次执行的语句序列。现在让我们看看如何使用所谓的函数来组合语句。
使用函数
函数用于给一组指令命名,以便可以在程序的不同地方使用。通常,在函数中,我们将一组用于执行特定任务的指令分组,例如:
-
显示前 10 个整数的列表。
-
计算前 10 个数字(从 0 到 9)的和。
-
计算前 N 个数字(从 0 到 N-1)的和。在这种情况下,
N将是函数的一个参数,因为每次调用(或使用)函数时它都可能改变(或使用)。
上文所述的函数非常简单,但展示了函数的作用是通过总结一句话来封装任何过程。赋予函数的名称象征着期望执行的动作,这允许开发者轻松理解指令序列(包括未参与开发的外部开发者)。让我们逐一讨论我们列出的三个函数。
显示前 10 个整数的函数
让我们编写第一个函数,该函数显示前 10 个整数的列表。我们将把这个函数命名为 display_10_first_integers()。名称必须尽可能明确,因为一个 JavaScript 程序由许多函数组成,这些函数的名称在程序中必须是唯一的(如果两个函数名称相同,则只考虑最后一个,因为它会覆盖前面的)。
函数是通过使用关键字 function,然后是函数的名称,然后是括号来定义的。然后,我们在随后的花括号中指示组成函数的指令。每次在程序中调用函数时,将执行这个指令块。
让我们编写函数 display_10_first_integers(),该函数显示前 10 个整数:
使用函数显示前 10 个整数(testnode.js 文件)
function display_10_first_integers() {
for (var i=0; i <= 10; i++) console.log("i = " + i);
}
函数使用 function 关键字定义,后跟函数名和括号。
函数声明被分组在接下来的花括号之间的块中。我们找到了之前的 for() 循环作为指令,但它也可能是 while() 循环,它们的工作方式相同。
假设它包含在 testnode.js 文件中,让我们运行这个程序:
图 1.18 – 使用函数显示 1 到 10 的数字
如前图所示,屏幕保持空白,因为在控制台中没有注册任何显示。
事实上,我们只是简单地定义了函数,但我们还必须使用它,即在程序中调用它。你可以按需多次调用它——这是函数的目的:我们应该能够在任何时间调用(或使用)它们。但至少必须调用一次;否则,它就毫无用处,如前图所示。
让我们在函数定义之后添加函数调用:
函数的定义和调用
// function definition
function display_10_first_integers() {
for (var i=0; i <= 10; i++) console.log("i = " + i);
}
// function call
display_10_first_integers();
前面代码的结果可以在以下图中看到:
图 1.19 – 调用 display_10_first_integers() 函数
有趣的是,函数可以在程序的多个地方调用。让我们在以下示例中看看如何:
依次调用 display_10_first_integers() 函数
// function definition
function display_10_first_integers() {
for (var i=0; i <= 10; i++) console.log("i = " + i);
}
// function call
console.log("*** 1st call *** ");
display_10_first_integers();
console.log("*** 2nd call *** ");
display_10_first_integers();
console.log("*** 3rd call *** ");
display_10_first_integers();
在前面的代码中,函数连续调用了三次,显示前 10 个整数的列表多次。每次调用前的顺序如下所示:
图 1.20 – 依次调用 display_10_first_integers() 函数
计算前 10 个整数和的函数
现在我们想要创建一个函数,该函数计算前 10 个整数的和,即 1+2+3+4+5+6+7+8+9+10。结果是 55。这将使我们能够展示函数如何将结果返回到外部(即使用它的程序)。在这里,函数应该返回 55。
让我们调用函数 add_10_first_integers()。它可以写成以下形式:
添加前 10 个整数的函数
// function definition
function add_10_first_integers() {
var total = 0;
for (var i = 0; i <= 10; i++) total += i;
return total;
}
// function call
var total = add_10_first_integers();
console.log("Total = " + total);
我们在函数中定义了 total 变量。因为这个变量是使用 var 或 let 关键字定义的,所以它是函数的局部变量。这允许这个 total 变量与函数外部定义的变量不同,即使名称相同。
注意
如果函数中的 total 变量不是使用 var 或 let 关键字定义的,它将创建一个所谓的全局变量,该变量即使在函数外部也可以直接访问。这不是好的编程,因为你希望尽可能少地使用全局变量。
该函数使用 for() 循环来累加前 10 个整数,然后使用 return 关键字返回这个总和。这个关键字使得在函数外部可以访问任何变量的值,在我们的例子中,是 total 变量。
让我们运行之前的程序。我们应该看到以下输出:
图 1.21 – 计算前 10 个整数的和
计算前 N 个整数和的函数
之前的函数不是很实用,因为它总是返回相同的结果。一个更有用的函数是计算前 N 个整数的和,其中 N 可以在每次函数调用时不同。
在这种情况下,N 将是函数的一个参数。它的值在使用函数时在括号中指示。
让我们调用 add_N_first_integers() 函数来计算这个和。参数 N 将在函数名称后面的括号中指示。一个函数可以使用多个参数,只需按顺序用逗号分隔即可。在我们的例子中,一个参数就足够了。
让我们编写 add_N_first_integers(n) 函数,并使用它来计算前 10 个、然后 25 个、然后 100 个整数的和。在函数连续调用过程中,将使用值 10、25 和 100 作为参数,并将替换函数定义中指示的参数 n:
添加前 N 个整数的函数
// function definition
function add_N_first_integers(n) {
var total = 0;
for (var i = 0; i <= n; i++) total += i;
return total;
}
// calculation of the first 10 integers
var total_10 = add_N_first_integers(10);
console.log("Total of the first 10 integers = " + total_10);
// calculation of the first 25 integers
var total_25 = add_N_first_integers(25);
console.log("Total of the first 25 integers = " + total_25);
// calculation of the first 100 integers
var total_100 = add_N_first_integers(100);
console.log("Total of the first 100 integers = " + total_100);
add_N_first_integers(n) 函数与之前编写的 add_10_first_integers() 函数非常相似。它使用括号中指示的参数 n,并且不像之前那样从 0 到 10 循环,而是从 0 到 n。根据调用函数时使用的 n 的值,循环将不同,函数返回的结果也将不同。
调用函数时,它传递参数 10、25,然后 100,正如所期望的那样。函数的 total 变量返回结果,然后由函数外部的 total_10、total_25 和 total_100 变量使用:
图 1.22 – 计算前 10 个、然后 25 个、然后 100 个整数的和
摘要
本章介绍了 JavaScript 的基本特性:不同类型的变量、条件测试、循环和函数。它们在客户端和服务器端都得到应用。
在下一章中,我们将探讨 JavaScript 的更多深入特性,例如使用 JavaScript 进行面向对象编程。
第二章:第二章:探索 JavaScript 的高级概念
在本章中,我们将探讨 JavaScript 的高级特性,例如面向对象编程。我们还将研究 JavaScript 中广泛使用的两种类型的对象:数组和字符串。最后,我们将看到 JavaScript 如何允许你使用所谓的回调函数来触发延迟处理。
在本章中,我们将涵盖以下主题:
-
类和对象
-
数组
-
字符串
-
多任务
-
使用承诺
所有这些主题对于构建 JavaScript 应用程序都是基本的。现在让我们开始吧!
技术要求
你可以在 GitHub 上找到本章的代码文件:github.com/PacktPublishing/JavaScript-from-Frontend-to-Backend/blob/main/Chapter%202.zip。
类和对象
类和对象的概念是编程语言的基础。JavaScript 允许它们被使用。
类用于表示任何类型的数据。例如,人、客户、汽车等等。我们可以定义一个类来表示这些类型的元素,例如,一个Person类来表示人,一个Client类来表示客户,一个Car类来表示汽车。
注意
注意,类名传统上以大写字母开头。
另一方面,一个对象将是类的一个特定元素(这个元素也将被称为实例)。例如,在Person类中的所有人中,被其名字“Clinton”和名字“Bill”所标识的人代表了这个类Person的一个特定对象。这个对象可以与程序中的变量p相关联。因此,我们可以创建变量来标识与该类关联的每个对象。
定义一个类
创建类时,你需要问自己的问题是,你想要对这个数据类型执行哪些操作。
例如,如果我们创建Person类,我们应该问什么特征可以定义一个人,以及我们可以在该类上执行哪些操作。例如,我们可以说Person类由人的姓氏、名字和年龄来定义。你也可以添加地址、电话号码、电子邮件等。
至于对人的可能操作,我们可以想象,例如,与另一个人结婚的操作,搬到另一个城市的操作,更换雇主的操作等等。
注意
姓氏、名字、年龄等这样的特征被称为类的属性,而结婚、搬家等这样的操作被称为类的方法。因此,一个类将把一组属性和一组方法组合在一起。
使用class关键字后跟类名,然后是描述内容的括号来创建 JavaScript 类。例如,Person类的创建如下所示:
人员类
class Person {
}
这种 Person 类的定义现在不会很有用,因为它内部没有定义属性或方法。我们将在以后看到如何改进它。
通过使用类创建对象
一旦定义了类,我们就可以创建与该类关联的对象。为此,我们使用关键字 new 后跟类的名称。这创建了一个表示该类对象的变量:
创建 Person 类的对象 p
// define the Person class
class Person {
}
// create an object of class Person
var p = new Person; // object p of class Person
console.log(p);
这是你将看到的内容:
图 2.1 – 创建 Person 类对象
p 对象在控制台中显示。我们被告知它是一个 Person 类对象,它是空的 {}。对象以花括号形式表示的传统在 JavaScript 中,正如我们在上一章的 JavaScript 中使用的变量类型 部分中看到的。
我们可以验证它也可以在客户端,在浏览器中工作。HTML 文件如下:
index.html 文件
<html>
<head>
<meta charset="utf-8" />
<script>
class Person {
}
var p = new Person;
console.log(p);
</script>
</head>
<body>
</body>
</html>
图 2.2 – 在浏览器中创建对象
我们找到了花括号的显示,这表示了 JavaScript 对象的显示。
不使用类创建对象
没有先创建类,也可以创建对象。你只需使用花括号 { 和 } 的符号。
例如,我们可以编写以下内容:
使用花括号符号创建对象
var p = { lastname : "Clinton", firstname : "Bill" };
console.log("The person is", p);
这将创建具有 lastname 和 firstname 属性的对象 p。请注意,你可以通过将属性名称用引号括起来或不括起来来指定属性名称。因此,{ lastname: "Clinton" } 也可以写成 { "lastname": "Clinton" },通过将 lastname 属性用单引号或双引号包围。
现在让我们看看如何通过向其中添加属性和方法来改进之前创建的 Person 类。
向类中添加属性
在我们的例子中,一个人有一个姓氏、一个名字和一个年龄。我们将为 Person 类的人创建这三个属性。
你只需在 Person 类的主体中按名称指示这些属性即可。首先,不要使用 var 或 let 关键字来定义它们:
在 Person 类中添加 firstname、lastname 和 age 属性
class Person {
firstname;
lastname;
age;
}
var p = new Person;
console.log(p);
图 2.3 – 在 Person 类中创建 lastname、firstname 和 age 属性
Person 类对象 p 现在具有在类中添加的属性。这个类的任何其他对象也将具有它们。
注意,添加的属性值是 undefined。这是正常的,因为这些属性在 p 对象或 Person 类中尚未指定值。
让我们修改 Person 类,以便属性具有默认值,而不是 undefined:
具有默认值的属性
class Person {
firstname = "";
lastname = "";
age = 0;
}
var p = new Person;
console.log(p);
每个属性都使用其默认值进行初始化。lastname 和 firstname 属性使用空字符串 "" 进行初始化,而 age 默认初始化为 0。
图 2.7 – 使用构造函数
我们发现 lastname 和 firstname 属性已初始化,但 age 属性现在初始化为 undefined 而不是 0。要为其分配另一个值,只需在创建对象时传递一个额外的值。这个额外的值将代表人的年龄,例如:
在创建 Person 类对象时使用年龄
class Person {
// class properties
lastname = "";
firstname = "";
age = 0;
// class methods
constructor(lastname, firstname, age) {
this.lastname = lastname;
this.firstname = firstname;
this.age = age;
}
display() {
// the age of the person is also displayed
console.log("The person's lastname = " + this.lastname +
", firstname = " + this.firstname +
", age = " + this.age);
}
}
var p = new Person("Clinton", "Bill", 33); // age is now
// transmitted
console.log("Variable p = ", p);
p.display();
图 2.8 – 人的年龄现在被传输
我们已经看到了如何通过直接使用类定义其属性和方法来创建一个对象。然而,我们也可以从一个对象创建另一个对象。让我们看看如何做到这一点。
合并一个对象与另一个对象
有可能存在需要从一个旧对象创建新对象的情况。让我们看看如何做到这一点。
如果对象 p 包含一个值,则语句 var p2 = p 并不会创建一个新的对象 p2,它与对象 p 是不同的,而只是一个指向与引用 p 相同值的引用 p2。因此,对对象 p 的属性所做的任何修改都将反映在对象 p2 中,因为它们都指向相同的内存位置。
这可以通过以下示例进行验证:
修改内存中的对象
var p = { lastname : "Clinton", firstname : "Bill" };
console.log("p (before modification of p2) =", p);
// p = { lastname : "Clinton", firstname : "Bill" }
var p2 = p;
p2.city = "Washington";
console.log("p (after modification of p2) =", p);
// p = { lastname : "Clinton", firstname : "Bill",
// city : "Washington"}
console.log("p2 =", p2);
// p2 = { lastname : "Clinton", firstname : "Bill",
// city : "Washington"}
即使只修改了 p2 对象,p 对象也会被修改,因为它们是内存引用,指向相同的位置。如果内存位置的内容发生变化,两个引用都会看到相同的变化。
为了避免这种情况,不需要编写 p2 = p,而是将对象 p 的属性复制到对象 p2 的属性中,从而创建一个新的内存位置。为此,JavaScript 提供了扩展运算符,其形式为 …,它允许这样做:
使用扩展运算符 …
var p = { lastname : "Clinton", firstname : "Bill" }
console.log("p (before modification of p2) =", p);
var p2 = { ...p}; // copy the properties of object p into
// object p2
p2.city = "Washington";
console.log("p (after modification of p2) =", p);
console.log("p2 =", p2);
扩展运算符通过在大括号{}周围包围原始对象,并在对象前使用扩展运算符(例如,{...p})来使用。
图 2.9 – 使用扩展运算符…
当对象p2被修改时,对象p不再被修改。
也可以用简化的形式来写:
从对象p创建对象p2,添加城市
// to avoid writing p2.city = "Washington"
var p2 = { ...p, city : "Washington" };
现在我们已经了解了类和对象以及如何使用它们,让我们看看一个重要的类对象:Array类。
数组
数组按照它们的索引顺序存储数据集合。索引也称为数组的索引。它从 0 开始,增加到数组的总元素数减 1(0 到 n-1)。
让我们首先学习如何创建一个数组。
使用方括号创建数组
在 JavaScript 中,数组对应于Array类对象。因此,我们使用new Array指令来创建一个数组。
然而,由于数组在 JavaScript 程序中广泛使用,因此也可以使用方括号表示法[和]来创建它们。这是一种更简单的方法,无需通过Array类即可使用它们。
让我们详细看看这两种创建数组的方式(使用方括号和使用Array类)。
使用方括号[和]创建数组
创建数组最简单、最快的方法是使用方括号表示法:
使用方括号创建数组
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log(tab);
数组以一个开方括号[开始,以一个闭方括号]结束。数组中的元素由逗号分隔。我们在这里插入的是字符串元素,但实际上,任何类型的元素都可以插入到数组中。
图 2.10 – 插入到数组中的元素
注意,可以创建一个空数组(没有任何元素)。我们将其写为[],不在方括号内指定任何元素。然后就可以向这个数组中添加元素。
使用Array类创建数组
您还可以使用Array类来创建数组。Array类包括一个构造函数,其中我们指定数组元素的列表,每个元素之间由逗号分隔。
可以通过new Array语句以以下方式创建与之前相同的数组:
使用new Array创建数组
var tab = new Array("Element 1", "Element 2", "Element 3", "Element 4", "Element 5");
console.log(tab);
图 2.11 – 使用new Array创建数组
创建的数组与之前相同。
要创建一个空数组,只需在构造函数中不传递任何参数,如下所示:
使用new Array()创建空数组
var tab = new Array(); // or new Array;
console.log(tab);
![图 2.12 – 创建空数组 []
](https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/js-fe-be/img/Figure_2.12_B17416.jpg)
图 2.12 – 创建空数组 []
现在我们已经看到了如何创建一个数组,接下来让我们看看如何访问它的每个元素。
访问数组元素
在之前的程序中,我们使用console.log(tab)语句显示了整个数组。可以单独访问数组的每个元素。每个元素可以按以下方式访问:
-
通过索引
-
使用
for()循环 -
使用
forEach()方法
让我们看看这三种方法中的每一种。
通过索引访问元素
让我们以之前包含五个元素的数组为例,即tab = ["元素 1", "元素 2", "元素 3", "元素 4", "元素 5"]:
-
第一个元素可以通过其索引 0 访问,即
tab[0]。 -
下一个,索引为 1 的,将通过
tab[1]访问。 -
最后一个,索引为 4 的,将通过
tab[4]访问。
这是您显示每个元素的方式:
通过索引显示数组中的每个元素
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log("tab =", tab);
console.log("tab[0] =", tab[0]);
console.log("tab[1] =", tab[1]);
console.log("tab[2] =", tab[2]);
console.log("tab[3] =", tab[3]);
console.log("tab[4] =", tab[4]);
console.log("tab[5] =", tab[5]);
结果将在以下图中显示:
图 2.13 – 通过索引显示每个元素
该数组包含五个元素,这意味着索引从 0 到 4。然而,为了进行测试,我们也访问了索引为 5 的元素。可以访问数组中不存在的元素的索引。在这种情况下,结果是 JavaScript 值undefined,这意味着此元素的值尚未分配。
注意,使用这种访问方法可以修改数组元素的值——只需给它一个新的值:
修改数组索引 2 和 3 中元素的值
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log("Array before modification");
console.log("tab =", tab);
// modification of elements, index 2 and 3
tab[2] = "New element 3";
tab[3] = "New element 4";
console.log("Array after modification");
console.log("tab =", tab);
这是结果:
图 2.14 – 修改数组元素
接下来,我们将查看使用for()或while()循环访问元素。
使用 for()或 while()循环访问元素
在上一章中已经研究过的for()和while()循环允许您浏览数组的所有元素。循环的索引从 0 开始(为了访问数组的第一个元素,即索引为 0 的元素)并结束于数组的最后一个索引。
要知道最后一个索引,JavaScript 在Array类中提供了length属性,这允许我们知道数组中元素的总数。最后一个索引将是值为length – 1的索引:
使用 for()循环访问数组元素
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log("tab =", tab);
console.log("Access to each element by a for() loop");
for (var i = 0; i < tab.length; i++) console.log("tab[" + i + "]=", tab[i]);
注意,循环的结束是通过测试值i < tab.length来写的。这相当于写i <= tab.length – 1。
图 2.15 – 使用 for()循环访问数组元素
接下来,我们将查看使用forEach(callback)方法访问元素。
使用 forEach(callback)方法访问元素
forEach(callback)方法是由 JavaScript 在Array类上定义的方法。它通过将数组的每个元素传递给作为参数传递的函数来遍历数组的元素。因此,作为参数指定的函数可以访问数组的每个元素(如果需要,还可以访问其索引)。
回调函数
在 JavaScript 中,将函数作为方法参数的指示原则非常常见。参数中的函数被称为回调函数,这意味着实际要执行的处理是在回调函数中指定的。
我们在这里展示了如何使用forEach(callback)方法参数中指定的回调函数。
我们使用之前看到的五个元素的tab数组,并对其应用forEach()方法:
使用forEach()方法访问数组元素
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log("tab =", tab);
console.log("Access to each element by the forEach() method");
tab.forEach(function(elem, i) {
console.log("tab[" + i + "]=", elem);
});
我们将函数作为forEach()方法的参数。这个所谓的回调函数将由 JavaScript 自动为tab数组(使用forEach()方法)的每个元素调用。
回调函数将其第一个参数作为被调用的函数的数组元素(参数elem),以及其索引(参数i)。
图 2.16 – 使用forEach()方法访问数组元素
结果与for()循环得到的结果相同。然而,我们立即发现了一个(小)差异。
for()循环和forEach()方法的区别
之前的程序在访问数组元素时没有显示for()循环和forEach()方法结果之间的任何差异。
为了展示这两种方法之间的区别,让我们在数组中引入一个新的元素,在索引 10 处,我们知道在创建数组时使用的最后一个索引是 4。因此,我们创建了一个比数组当前最后一个元素远得多的新元素。数组会如何对这个扩展做出反应?
在索引 10 处添加元素
// original array
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
// adding a new element in the array, at index 10
tab[10] = "Element 9";
console.log("tab =", tab);
// display the array with a for() loop
console.log("Access to each element by a for() loop");
for (var i = 0; i < tab.length; i++) console.log("tab[" + i + "]=", tab[i]);
// display the array by the forEach() method
console.log("Access to each element by the forEach() method");
tab.forEach(function(elem, i) {
console.log("tab[" + i + "]=", elem);
});
我们使用tab[10] = "Element 9"向数组中添加一个元素,然后使用for()循环和forEach()方法显示数组的所有内容。
结果显示在下图中:
图 2.17 – 在数组索引 10 处添加元素
for()循环的显示表明索引 5 到 9 的元素存在,但它们的值为undefined,因为实际上没有为这些数组的索引插入任何值。然而,for()循环显示了具有undefined值的索引 5 到 9。
相反,forEach()方法只提供参数中指定的回调函数,该回调函数具有数组中实际受影响的数组元素。因此,避免了索引 5 到 9 的元素,这些元素在程序中没有分配。
我们已经看到了如何创建一个数组,然后如何访问其每个元素。现在让我们看看如何向数组中添加新元素。
向数组中添加项目
一旦创建数组(空或非空),就可以向其中添加元素。我们将主要使用以下两种技术之一:
-
在数组中通过索引添加元素
-
使用 push() 方法添加项目
现在,让我们来看看这两种技术。
通过索引添加元素
这对应于赋值 tab[i] = value。我们之前在写 tab[10] = "Element 9" 时使用了它。
注意,如果使用的索引大于数组中当前元素的数量,这将通过创建初始化为值 undefined 的新元素来扩大数组。如果使用的索引小于数组中的元素数量,它将修改目标元素的当前值。
使用 push() 方法添加元素
push() 方法定义在 Array 类中。它允许你添加一个新元素到数组中,而无需担心插入索引,因为它会自动将元素插入数组的末尾:
使用 push() 方法插入元素
// original array
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
// insert an element using the push() method
tab.push("Element 6");
console.log("tab =", tab);
// display the array with a for() loop
console.log("Access to each element by a for() loop");
for (var i = 0; i < tab.length; i++) console.log("tab[" + i + "]=", tab[i]);
// display the array by the forEach() method
console.log("Access to each element by the forEach() method");
tab.forEach(function(elem, i) {
console.log("tab[" + i + "]=", elem);
});
指令 tab.push("Element 6") 将此元素插入数组的末尾。然后使用之前看到的各种方法显示数组。
图 2.18 – 使用 push() 方法添加元素
我们知道如何添加和修改数组中的元素。剩下的就是知道如何从数组中删除元素。
删除数组元素
JavaScript 允许我们以两种方式删除数组元素:
-
删除数组中元素的值,同时保留具有
undefined值的元素在数组中 -
从数组中删除元素本身
现在我们来探讨这两种可能性。
删除元素值(不删除数组中的元素)
我们使用 delete 关键字来删除数组中元素的值。例如,delete tab[0] 通过将其赋值为 undefined 来删除数组 tab 中索引为 0 的元素的值。元素没有被从数组中删除,数组仍然保留与之前相同的元素数量:
删除索引为 0 的元素的值
// original array
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
// delete the value of the element with index 0
delete tab[0];
console.log("tab =", tab);
// display the array with a for() loop
console.log("Access to each element by a for() loop");
for (var i = 0; i < tab.length; i++) console.log("tab[" + i + "]=", tab[i]);
// display the array by the forEach() method
console.log("Access to each element by the forEach() method");
tab.forEach(function(elem, i) {
console.log("tab[" + i + "]=", elem);
});
图 2.19 – 删除索引为 0 的元素的值
我们可以看到,for() 循环显示了元素的 undefined 值,而 forEach() 方法不再显示元素,因为它的值已经被删除。
注意
注意,如果我们不使用 delete tab[0],而是使用 tab[0] = undefined,则 forEach() 方法将索引 0 的元素显示为数组的第一个元素,因为元素的值实际上并没有被删除,而是被分配了新的值,这里为 undefined。
现在我们来看第二种从数组中删除元素的方法。
从数组中删除元素
使用 delete 关键字不会从数组中删除元素,数组保留相同的元素数量。
在 Array 类中定义的 splice(begin, count) 方法允许你从数组中物理删除元素,因此在使用后数组将至少少一个元素。
splice(begin, count) 方法包含 begin 和 count 参数,这允许你指定从哪个索引开始删除(begin 参数)元素,以及你想要删除的连续元素的数量(count 参数)。
因此,要从数组 tab 中删除索引为 0 的元素,只需编写 tab.splice(0, 1):
使用 splice() 方法删除数组中的索引为 0 的元素
// original array
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
// remove 1 element from index 0
tab.splice(0, 1);
console.log("tab =", tab);
// display the array with a for() loop
console.log("Access to each element by a for() loop");
for (var i = 0; i < tab.length; i++) console.log("tab[" + i + "]=", tab[i]);
// display the array by the forEach() method
console.log("Access to each element by the forEach() method");
tab.forEach(function(elem, i) {
console.log("tab[" + i + "]=", elem);
});
这是你将看到的内容:
图 2.20 – 删除索引为 0 的元素
我们已经看到了如何在数组中添加和删除元素。现在让我们看看如何从当前数组中的元素提取一个新的数组。
在数组中过滤元素
过滤数组元素是常见的操作,例如,保留特定元素或返回新的元素。Array 类有两个方法——filter(callback) 和 map(callback)——允许我们根据条件返回一个新数组。
使用 filter(callback) 方法
tab.filter(callback) 方法返回一个新数组,同时只保留 tab 数组中所需的元素。
形式为 callback(element, index) 的回调函数会对数组 tab 的每个元素进行调用。如果决定保留该元素,则必须返回 true;否则,该元素将被排除。tab.filter() 方法返回一个新数组作为结果,但原始 tab 数组不会被修改(除非在方法返回时将其赋值,如下例所示)。
让我们使用 filter() 方法来保留数组中索引大于或等于 2 的元素:
使用 filter() 方法
// original array
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log("initial tab =", tab);
// keep only items with index >= 2
tab = tab.filter(function(element, index) {
if (index >= 2) return true; // keep this element
});
console.log("\nfinal tab =", tab);
如果回调函数返回 true,则保留该元素;否则,将其排除。回调函数也可以返回 false,甚至可以返回空值,就像这里一样,在这种情况下,该元素将被排除:
图 2.21 – 使用 filter() 方法
这就结束了 filter() 方法的介绍。
使用 map(callback) 方法
tab.map(callback) 方法用于从初始 tab 数组的元素返回一个新数组。初始数组中的每个元素都会传递给形式为 callback(element, index) 的回调函数,该回调函数必须为每个元素返回一个新元素,该元素将替换原始元素。
让我们使用 map(callback) 方法来返回一个新数组,其中所有元素都已大写:
使用 map() 方法
// original array
var tab = ["Element 1", "Element 2", "Element 3", "Element 4", "Element 5"];
console.log("initial tab =", tab);
// capitalize all elements
tab = tab.map(function(element, index) {
return element.toUpperCase();
});
console.log("\nfinal tab =", tab);
toUpperCase() 方法是在 String 类上定义的方法(以下截图),允许你将使用该方法字符字符串大写。
结果显示在下图中:
图 2.22 – 使用 map() 方法
在本节中,我们研究了使用 Array 类的对象。JavaScript 中还有另一类广泛使用的对象:字符串,它们由 String 类表示。现在让我们看看如何使用 String 类的对象。
字符串
字符串在编程语言中被广泛使用。它们用于表示用户输入的文本或将要显示给用户的文本。
创建一个字符串
字符串由 String 类的对象表示。但由于字符串在 JavaScript 中被广泛使用,因此语言允许通过用双引号 " " 或单引号 ' ' 包围它们来使用它们。在某些情况下,也可以使用反引号(反向引号 ' ')。
注意
在这种情况下,字符串字面量必须以相同类型的引号开始和结束。
现在让我们看看如何使用这些不同的方法创建字符串。
使用双或单引号创建字符串字面量
创建字符串字面量的最简单方法是使用单引号或双引号记法:
使用双引号创建字符串字面量
var s = "String 1";
console.log("s =", s);
或者,使用单引号:
使用单引号创建字符串字面量
var s = 'String 1';
console.log("s =", s);
在这两种情况下,显示的字符字符串是相同的。
图 2.23 – 创建字符字符串
使用单/双引号选项的优势
如果字符串本身包含引号,使用单或双引号的可能性优势就显而易见了。例如,如果字符串是 "I'll love JavaScript",使用单引号创建字符串将产生错误,因为字符串将被假定以单词 I'll 中的撇号结束。在这种情况下,你必须使用双引号以避免错误。
使用反引号创建字符串字面量
你也可以使用反引号。这在需要以更简单的方式在字符串中使用变量的值时非常有用。
例如,假设你想显示一个包含一个人的姓和名的字符串。姓和名分别存储在名为 lastname 和 firstname 的变量中:
连接字符串和变量
var lastname = "Clinton";
var firstname = "Bill";
// old way of concatenating strings and variables
var s1 = "lastname is " + lastname + ", firstname is " + firstname;
// new way of concatenating strings and variables
var s2 = `lastname is ${lastname}, firstname is ${firstname}`;
console.log("s1 =", s1);
console.log("s2 =", s2);
当使用反向引号时,不再使用 + 符号来连接字符串和变量。所有内容都写在一个字符串中,变量通过“符号” ${variable} 来识别。
大括号 { 和 } 之间可以是一个简单的变量(如这里所示),也可以是一个更复杂的可以计算的 JavaScript 表达式(例如,{a+b})。
我们可以看到,这两个结果字符串是相同的。
图 2.24 – 使用 String 类创建字符串的字符字符串和变量序列
最后,可以使用 String 类来创建字符字符串。String 类有一个构造函数,其中要构造的字符串作为参数指示:
使用 String 类
var s = new String("I'll love JavaScript");
console.log("s =", s);
下图显示了结果:
图 2.25 – 使用 String 类
String 类具有属性和方法。例如,length 属性可以让你知道字符串中的字符数,因此可以比较,例如,两个字符字符串的长度。
让我们使用 length 属性来显示使用引号和 String 类创建的两个字符串的长度:
使用 String 类的 length 属性
var s1 = new String("I'll love JavaScript");
var s2 = "I'll love JavaScript";
console.log("s1 =", s1);
console.log("s2 =", s2);
console.log("s1.length =", s1.length);
console.log("s2.length =", s2.length);
这是结果:
图 2.26 – 使用 String 类的 length 属性
无论字符串是如何创建的,它的长度都是相同的(这里,20 个字符)。我们已经看到了如何创建字符字符串,现在让我们看看如何访问组成它的字符。
访问字符串中的字符
String 类定义了用于访问字符串中字符的方法。特别是,这些方法是 charAt(index) 和 slice(start, end)。charAt(index) 用于检索字符串中索引指示的字符,从索引 0 开始。最大索引是 length 属性的值减去 1。slice(start, end) 通过提取从 start 索引(包含)到 end 索引(排除)的字符来将字符串分割成子字符串。
使用 charAt(index) 方法
让我们使用 charAt(index) 方法逐个显示字符串中的字符:
显示字符串中的字符
var s = "Hello";
console.log("s =", s);
for (var i = 0; i <s.length; i++) console.log(`s.charAt(${i}) = ${s.charAt(i)}`);
注意使用反引号来显示结果字符串。
结果显示在下图中:
图 2.27 – 使用 charAt() 方法
现在,让我们看看 slice(start, end) 方法。
使用 slice(start, end) 方法
之前的 charAt(index) 方法从字符串中检索单个字符,而 slice(start, end) 方法可以检索多个连续的字符:
注意
注意,slice(start, end) 方法不会修改应用该方法的原字符串,而是返回一个新的字符串。原始字符串不会被修改,因此可以保持完整。
在“Hello”字符串上使用 slice()
var s = "Hello";
console.log("s =", s);
console.log(`s.slice(0,2) = ${s.slice(0,2)}`);
console.log(`s.slice(0,3) = ${s.slice(0,3)}`);
console.log(`s.slice(1,3) = ${s.slice(1,3)}`);
console.log(`s.slice(0,-1) = ${s.slice(0,-1)}`);
console.log(`s.slice(0,-2) = ${s.slice(0,-2)}`);
console.log(`s.slice(1,-2) = ${s.slice(1,-2)}`);
如果 slice(start, end) 方法的 end 索引(第二个参数)是负数,这意味着计数从字符串的末尾开始(如果它是正数,则从开头开始)。
我们得到以下结果:
图 2.28 – 使用 slice() 方法
现在我们已经看到了如何获取组成字符串的字符,让我们看看如何修改字符串。
修改字符字符串
要修改字符串,只有一个可能性:必须从它构建一个新的字符串。原始字符串不能直接更改。
因此,我们将使用之前的slice()和charAt()方法,这将使我们能够提取原始字符串的部分,以便构建结果字符串。
但为了搜索或修改字符字符串的部分,最好使用正则表达式。我们将在下面学习它们。
使用正则表达式
正则表达式与字符串相关。它们用于检查字符串是否具有某种格式(例如,电子邮件格式、电话号码格式等),或者用其他字符替换这些格式的字符。
因此,String类有match(regexp)方法来检查字符字符串是否具有特定的格式,以及replace(regexp, str)方法来将此格式中的字符串部分替换为新字符串str。
在这两种方法中,regexp参数对应于正则表达式,其含义我们将在下面学习。
检查字符串是否具有特定的格式
match(regexp)方法用于检查方法所用的字符字符串是否与regexp中指示的格式相符。regexp参数被称为正则表达式。
正则表达式
正则表达式是由/和/包围的字符序列,例如,/abc/。正则表达式/abc/表示我们在字符字符串中寻找字符序列abc。如果字符串包含序列abc,则match(/abc/)方法将返回此字符序列作为结果,否则返回值null。
正则表达式的完整描述可以在developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/RegExp找到。
下面是一些正则表达式的示例,以及当在字符串"Hello"上使用match()方法时返回的值:
使用 match(regexp)
var s = "Hello";
console.log("s =", s);
// search for "Hel"
console.log(`s.match(/Hel/) = ${s.match(/Hel/)}`);
// search for "hel"
console.log(`s.match(/hel/) = ${s.match(/hel/)}`);
// search for "hel" ignoring upper/lower case
console.log(`s.match(/hel/i) = ${s.match(/hel/i)}`);
// search for H followed by a or b or e followed by l
console.log(`s.match(/H[abe]l/) = ${s.match(/H[abe]l/)}`);
// search for He followed by 0 or 1 a followed by l
console.log(`s.match(/Hea?l/) = ${s.match(/Hea?l/)}`);
// search for He followed by 0 (min) to 1 (max) followed by l
console.log(`s.match(/Hea{0,1}l/) = ${s.match(/Hea{0,1}l/)}`);
// search for He followed 1 (min) to 2 (max) followed by l
console.log(`s.match(/Hea{1,2}l/) = ${s.match(/Hea{1,2}l/)}`);
当正则表达式在"Hello"字符串中找到时,match()方法将返回找到的字符串部分,否则返回null。
正则表达式末尾的i标志表示必须忽略大小写字母。
一系列字母周围的方括号[和]表示只需要这些字母中的一个。
问号?表示前面的字符是可选的(它可以出现,也可以不出现)。
大括号{min,max}表示前面的字符至少出现min次,最多出现max次。
之前程序的输出如下:
图 2.29 – 使用正则表达式
注意
编写正则表达式有时可能比较复杂。网站regex101.com/允许您测试您想要的正则表达式。
正则表达式也可以使用 replace() 方法修改字符字符串的部分。
使用 replace() 方法替换字符串的一部分
replace(regexp, str) 方法用于将字符串中符合正则表达式 regexp 格式的部分替换为字符串 str。它返回一个新的字符串,而原始字符串不会被修改。如果正则表达式指示的格式未找到,则返回原始字符串,不做任何修改。
让我们取前一个示例中的正则表达式,并使用正则表达式将找到的字符串替换为字符串“abc”:
使用 replace() 方法
var s = "Hello";
console.log("s =", s);
// search for "Hel" and replace with "abc"
console.log(`s.replace(/Hel/, "abc") => ${s.replace(/Hel/, "abc")}`);
// search for "hel" and replace with "abc"
console.log(`s.replace(/hel/, "abc") => ${s.replace(/hel/, "abc")}`);
// search for hel ignoring upper/lower case and replacing with
// "abc"
console.log(`s.replace(/hel/i, "abc") => ${s.replace(/hel/i, "abc")}`);
// search for H followed by a or b or e followed by l and
// replace with "abc"
console.log(`s.replace(/H[abe]l/, "abc") => ${s.replace(/H[abe]l/, "abc")}`);
// search for He followed by 0 or 1 a followed by l and
// replaced by "abc"
console.log(`s.replace(/Hea?l/, "abc") => ${s.replace(/Hea?l/,
"abc")}`);
// search for He followed by 0 (min) to 1 (max) followed by l
// and replaced by "abc"
console.log(`s.replace(/Hea{0,1}l/, "abc") => ${s.replace(/Hea{0,1}l/, "abc")}`);
// search for He followed by 1 (min) to 2 (max) followed by l
// and replaced by "abc"
console.log(`s.replace(/Hea{1,2}l/, "abc") => ${s.replace(/Hea{1,2}l/, "abc")}`);
输出如下所示:
图 2.30 – 使用 replace() 方法
所有之前的程序执行都是立即执行的。我们现在将研究如何执行延迟处理。
JavaScript 中的多任务处理
当你开始用 JavaScript 编码时,经常会遇到一个问题:是否可以同时执行多个过程(在计算机中称为多任务处理)?如果需要执行的过程将花费很长时间,这将非常有用,以免阻塞其他同样紧急的过程。
JavaScript 不允许同时执行多个处理操作。另一方面,可以通过使用回调函数(在我们研究 使用 forEach(callback) 方法访问元素 部分时已经讨论过的)不阻塞程序(在浏览器客户端和 Node.js 服务器端)。
回调函数
回调函数对应于用作 JavaScript 方法或函数参数的处理函数。回调函数将由使用它的方法或函数在期望的时间执行。
Node.js 广泛使用此功能。例如,在读取文件时,readFile(callback) 方法在文件被读取时将回调函数作为参数调用,这允许程序不阻塞待读取文件的挂起处理。
JavaScript 定义了两个主要的标准函数,它们使用此回调函数概念:setTimeout() 和 setInterval() 函数。这两个函数都使用回调函数作为参数。我们将在下面描述这两个函数。
使用 setTimeout() 函数
setTimeout(callback, timeout) 函数用于定位一个将在 timeout(以毫秒为单位)表示的时间间隔过去后执行的执行函数(callback 函数)。
这允许你,例如,在 5 秒后(即 5,000 毫秒)执行处理。你可以在等待这个延迟的同时执行其他指令,因此程序在这段时间内不会被阻塞:
延迟 5 秒后的处理指令
console.log("Before setTimeout()");
setTimeout(function() {
console.log("In the callback function");
}, 5000); // 5000 milliseconds, or 5 seconds
console.log("After setTimeout()");
我们在程序开始时在控制台中显示一条消息("Before setTimeout()")。我们编程一个 5 秒的延迟,之后触发一个回调函数,在控制台中显示另一条消息("In the callback function")。最后,我们通过显示一条新消息("After setTimeout()")来结束程序。
例如,使用 node testnode.js 命令运行此程序。要在浏览器中测试此程序,只需将前面的 JavaScript 代码放置在 index.html 文件的 <script> 和 </script> 标签之间。
以下截图显示了 1 秒后的显示效果:
Figure 2.31 – 使用 setTimeout()
注意,开始显示的消息和结束显示的消息是连续的,即使 5 秒的时间限制还没有结束。这表明程序没有被阻塞,等待超时到期。
以下截图显示了至少 5 秒后的显示效果(当 setTimeout() 方法中使用的延迟已过去)。
Figure 2.32 – 5 秒延迟后的显示
我们可以看到,当 5 秒的延迟过去后,setTimeout() 函数中注册的回调函数会自动由 setTimeout() 函数调用。
让我们通过显示消息显示的时间来改进程序。这使得可以验证是否遵守了 5 秒的时间限制:
显示消息发布的时间
console.log(time(), "Before setTimeout()");
setTimeout(function() {
console.log(time(), "In the callback function");
}, 5000); // 5000 = 5 seconds
console.log(time(), "After setTimeout()");
function time() {
// return time as HH:MM:SS
var date = new Date();
var hour = date.getHours();
var min = date.getMinutes();
var sec = date.getSeconds();
if (hour < 10) hour = "0" + hour;
if (min < 10) min = "0" + min;
if (sec < 10) sec = "0" + sec;
return "" + hour + ":" + min + ":" + sec + " ";
}
time() 函数用于生成一个包含 HH:MM:SS 格式的字符字符串。这个时间在每个显示在控制台中的消息的开头显示。
这里使用的 Date 类是一个 JavaScript 类,它允许你管理日期并提取小时、分钟和秒。
我们现在得到以下结果:
Figure 2.33 – 显示在控制台中消息显示的时间
我们可以清楚地看到,回调函数是在 setTimeout() 函数参数中指定的 5 秒周期的末尾执行的。
使用 setInterval() 函数
setInterval(callback, timeout) 函数与之前看到的 setTimeout() 函数类似。但与 setTimeout() 函数只在延迟结束时只执行一次回调函数不同,setInterval() 函数会在每次回调函数执行结束后设置一个新的延迟,从而重复执行回调函数。因此,回调函数会以固定的时间间隔执行。停止这个循环的唯一方法是使用 clearInterval() 函数。
setInterval() 函数对于定期运行进程非常有用。
让我们使用 setInterval() 函数每秒显示一个初始化为 1 的计数器的值。计数器每秒递增:
每秒增加计数器
console.log(time(), "Start of timer");
var count = 1;
setInterval(function() {
console.log(time(), `count = ${count}`);
count++;
}, 1000); // 1000 = 1 second
function time() {
// return time as HH:MM:SS
var date = new Date();
var hour = date.getHours();
var min = date.getMinutes();
var sec = date.getSeconds();
if (hour < 10) hour = "0" + hour;
if (min < 10) min = "0" + min;
if (sec < 10) sec = "0" + sec;
return "" + hour + ":" + min + ":" + sec + " ";
}
这是你将看到的内容:
图 2.34 – 每秒增加计数器
计数器每秒增加一次,无限进行。要停止这个无休止的循环,你必须使用一个新的 JavaScript 函数,即 clearInterval()。
使用 clearInterval() 函数
clearInterval(timer) 函数用于停止由 setInterval() 指令启动的循环。
注意
注意,可以通过多次调用 setInterval() 函数启动多个计时器。因此,clearInterval(timer) 函数必须指定它想要停止哪个计时器:timer 参数用于告诉它。
要做到这一点,setInterval() 函数返回将在调用 clearInterval(timer) 函数时使用的 timer 参数。
让我们使用 clearInterval() 函数在 count 计数器达到值 5 时停止计时器:
使用 clearInterval() 函数停止计时器
console.log(time(), "Start of timer");
var count = 1;
var timer = setInterval(function() {
console.log(time(), `count = ${count}`);
if (count == 5) {
clearInterval(timer); // timer stop
console.log(time(), "End of timer");
} else count++;
}, 1000);
function time() {
// return time as HH:MM:SS
var date = new Date();
var hour = date.getHours();
var min = date.getMinutes();
var sec = date.getSeconds();
if (hour < 10) hour = "0" + hour;
if (min < 10) min = "0" + min;
if (sec < 10) sec = "0" + sec;
return "" + hour + ":" + min + ":" + sec + " ";
}
回调函数的程序被修改:一旦计数器达到 5,计时器就会停止。否则,计数器会增加 1。
检查计数在 5 次后停止:
图 2.35 – 计数器计数 5 次后停止
在 setTimeout() 或 setInterval() 函数中使用的回调函数直接包含在每个函数的参数中。JavaScript 通过使用称为 Promise 的新类型对象来简化回调函数的编写。
使用 Promise
Promise 是使用回调函数的另一种方式。而不是将回调函数集成到方法调用中(作为参数),我们将其用作新 then(callback) 方法的参数。这种方式简化了使用回调函数的 JavaScript 代码的阅读。
要使用 then(callback) 方法,对象必须是 Promise 类对象。Promise 类是在 JavaScript 语言中定义的一个类。
Promise 类
Promise 类对象使用形式为 callback(resolve, reject) 的回调函数作为其构造函数的参数。
resolve 和 reject 参数是函数,它们将从 Promise 的回调中调用:
-
当
resolve()函数被调用时,它触发then(callback)方法。 -
当
reject()函数被调用时,它触发catch(callback)方法。
resolve() 函数必须被调用,否则 then(callback) 方法无法执行。另一方面,调用 reject() 函数是可选的,如果未使用,则 catch(callback) 方法将不会调用(因此不需要在程序中存在)。
多亏了 resolve 和 reject 参数,因此我们有执行成功情况(使用 then(callback) 方法)和失败情况(使用 catch(callback) 方法)的可能性。这种编写方式确保了 JavaScript 代码的可读性更高。
为了说明这一点,让我们以之前看到的setTimeout(callback, timeout)函数为例。这里的回调函数包含在方法调用中,我们希望使用 Promise 来避免这种情况。让我们编写新的wait(timeout)方法,它可以以wait(timeout).then(callback)的形式使用。现在,回调函数已经从wait()方法外部化。
当超时到期时,将调用在then(callback)方法中注册的回调函数。
这种写法比之前的setTimeout()写法更易读,因为它显示了在执行过程之前的时间延迟。
要实现这一点,wait(timeout)方法必须返回一个Promise对象:
创建 Promise 对象,然后使用 then()方法
function time() {
// return time as HH:MM:SS
var date = new Date();
var hour = date.getHours();
var min = date.getMinutes();
var sec = date.getSeconds();
if (hour < 10) hour = "0" + hour;
if (min < 10) min = "0" + min;
if (sec < 10) sec = "0" + sec;
return "" + hour + ":" + min + ":" + sec + " ";
}
function wait(sec) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(sec); // triggers the then() method
}, sec*1000);
});
}
console.log(time(), "Start of timer");
wait(2).then(function(sec) {
console.log(time(), `End of timer of ${sec} seconds`);
});
wait()方法通过return new Promise()语句返回一个Promise对象。在callback(resolve, reject)函数中,当我们认为then()方法可以执行时,我们调用resolve()函数,这里是在超时结束时。
可以为resolve()和reject()方法指定参数。这些参数将在then(callback)或catch(callback)方法中使用的回调函数中使用。例如,在这里,我们调用resolve(sec)方法,这使得我们可以在then()方法的回调函数中使用sec参数。
注意
注意,在我们的示例中未使用reject()函数,因为没有错误情况可以发生。然而,必须调用resolve()函数;否则,then()方法将永远不会执行。
time()函数用于显示每个过程的执行时间,以检查执行是否正确。
图 2.36 – 使用 then()方法
这就带我们结束了本章的内容。
摘要
在本章中,我们学习了与 JavaScript 相关的高级概念。
我们学习了如何使用类和对象,特别是Array和String类。我们还看到了如何延迟指令的执行。
在本书的其余部分,我们将发现与客户端应用开发相关的 Vue.js JavaScript 库的使用。
我们将看到如何利用在这里获得的知识,使我们能够在客户端和服务器端编程的各个方面使用这种语言。

被折叠的 条评论
为什么被折叠?



