四、基本 JavaScript 入门

在这一章中,我快速浏览了 JavaScript 语言应用于 Vue.js 开发的最重要的特性。我没有足够的空间来完整地描述 JavaScript,所以我把重点放在了你需要快速掌握并遵循本书中的例子的要点上。表 4-1 总结了本章内容。

表 4-1

章节总结

|

问题

|

解决办法

|

列表

| | --- | --- | --- | | 提供将由浏览器执行的指令 | 使用 JavaScript 语句 | five | | 将语句的执行延迟到需要的时候 | 使用 JavaScript 函数 | 6–8, 11, 12 | | 定义参数数量可变的函数 | 使用默认和 rest 参数 | 9, 10 | | 简洁地表达功能 | 使用粗箭头功能 | Fourteen | | 定义变量和常数 | 使用letconst关键字 | 15, 16 | | 使用 JavaScript 基本类型 | 使用stringnumberboolean关键字 | 17, 18, 20 | | 定义包含其他值的字符串 | 使用模板字符串 | Nineteen | | 有条件地执行语句 | 使用ifelseswitch关键字 | Twenty-one | | 比较价值观和身份 | 使用等式和标识运算符 | 22, 23 | | 转换类型 | 使用类型转换关键字 | 24–26 | | 分组相关项目 | 定义一个数组 | 27, 28 | | 读取或更改数组中的值 | 使用索引访问器符号 | 29, 30 | | 枚举数组的内容 | 使用for循环或forEach方法 | Thirty-one | | 展开数组的内容 | 使用扩展运算符 | 32, 33 | | 处理数组的内容 | 使用内置数组方法 | Thirty-four | | 将相关值收集到一个单元中 | 定义一个对象 | 35–37 | | 定义可以对对象的值执行的操作 | 定义一种方法 | 38, 39 | | 将属性和值从一个对象复制到另一个对象 | 使用Object.assign方法 | Forty | | 群组相关功能 | 定义一个 JavaScript 模块 | 41–49 | | 观察异步操作 | 定义一个Promise并使用asyncawait关键字 | 50–54 |

为本章做准备

对于这一章,我需要一个简单的 Vue.js 项目。我首先运行清单 4-1 中所示的命令来创建一个名为jsprimer的项目。

小费

你可以从 https://github.com/Apress/pro-vue-js-2 下载本章以及本书其他章节的示例项目。

vue create jsprimer --default

Listing 4-1Creating the Example Project

将创建项目,并下载和安装应用和开发工具所需的包,这可能需要一些时间才能完成。

注意

在撰写本文时,@vue/cli包已经发布了测试版。在最终发布之前可能会有一些小的变化,但是核心特性应该保持不变。有关任何突破性变化的详细信息,请查看本书的勘误表,可在 https://github.com/Apress/pro-vue-js-2 获得。

一旦创建了项目,使用您喜欢的编辑器将src文件夹中的main.js文件的内容替换为清单 4-2 中所示的语句。对于本章,重点是 JavaScript 而不是 Vue.js,并且不需要现有的代码,因为它初始化一个 Vue.js 应用。

console.log("Hello");

Listing 4-2Replacing the Content of the main.js File in the src Folder

此项目需要更改配置以覆盖项目的默认设置。将清单 4-3 中显示的语句添加到project.json文件中,该文件负责配置项目,我在第 10 章中对此进行了描述。

...
"eslintConfig": {
  "root": true,
  "env": {
    "node": true
  },
  "extends": [
    "plugin:vue/essential",
    "eslint:recommended"
  ],
  "rules": {
    "no-console": "off",

    "no-declare": "off",

    "no-unused-vars": "off"

  },
  "parserOptions": {
    "parser": "babel-eslint"
  }
},
"postcss": {
  "plugins": {
    "autoprefixer": {}
  }
},
...

Listing 4-3Changing the Project Configuration in the package.json File in the jsprimer Folder

这些改变禁用了我在本章的例子中使用的 JavaScript 特性的警告,由 JavaScript linter 产生,我在第 10 章描述了它。

保存对main.jspackage.json文件的更改;然后打开一个新的命令提示符,运行清单 4-4 中所示的命令,导航到项目文件夹,启动 Vue.js 开发工具。尽管我在本章中没有使用 Vue.js 特性,但我将利用 Vue.js 工具链,它简化了在浏览器中执行 JavaScript 代码的过程。

cd jsprimer
npm run serve

Listing 4-4Starting the Vue.js Development Tools

项目的初始准备需要一段时间,之后您会看到一条消息,告诉您应用已经准备好了。打开一个新的浏览器窗口并导航到http://localhost:8080,这将产生如图 4-1 所示的空窗口。

img/465686_1_En_4_Fig1_HTML.jpg

图 4-1

运行示例应用

如果你打开浏览器的 F12 开发工具并检查控制台选项卡,你会看到清单 4-2 中的语句产生了一个简单的结果,如图 4-2 所示。

img/465686_1_En_4_Fig2_HTML.jpg

图 4-2

浏览器控制台中的一条消息

本章中的所有示例都产生文本输出,所以我将只使用文本,而不是显示控制台选项卡的屏幕截图,如下所示:

[HMR] Waiting for update signal from WDS...
Hello

第一行是来自开发工具的消息,当检测到src文件夹中的更改时,它会自动重新加载浏览器。我不会在后续示例的输出中包含这一点。

使用语句

基本的 JavaScript 构建块是语句。每条语句代表一条命令,语句通常以分号(;)结束。分号是可选的,但是使用分号会使代码更容易阅读,并且允许在一行中有多个语句。在清单 4-5 中,我向 JavaScript 文件添加了语句。

console.log("Hello");

console.log("Apples");

console.log("This is a statement");

console.log("This is also a statement");

Listing 4-5Adding JavaScript Statements in the main.js File in the src Folder

浏览器依次执行每条语句。在本例中,所有语句都只是将消息写入控制台。结果如下:

Hello
Apples
This is a statement
This is also a statement

定义和使用函数

当浏览器收到 JavaScript 代码时,它会按照定义的顺序执行其中包含的语句。这就是上一个示例中发生的情况。main.js文件中的语句被逐一执行,所有语句都向控制台写入一条消息,所有语句都按照它们在main.js中定义的顺序执行。您还可以将语句打包到一个函数中,直到浏览器遇到一个调用该函数的语句,该函数才会被执行,如清单 4-6 所示。

const myFunc = function () {
    console.log("This statement is inside the function");
};

console.log("This statement is outside the function");

myFunc();

Listing 4-6Defining a JavaScript Function in the main.js File in the src Folder

定义一个函数很简单:使用const关键字,后跟您想要给函数起的名字,再加上等号(=)和function关键字,再加上括号(()字符)。您希望函数包含的语句用大括号括起来(字符{})。

在清单中,我使用了名称myFunc,该函数包含一个向 JavaScript 控制台写入消息的语句。在浏览器到达另一个调用myFunc函数的语句之前,函数中的语句不会被执行,如下所示:

...
myFunc();
...

当您保存对main.js文件的更改时,更新的 JavaScript 代码将被发送到浏览器,在浏览器中执行并产生以下输出:

This statement is outside the function
This statement is inside the function

您可以看到函数内部的语句并没有立即执行,但是除了演示函数是如何定义的以外,这个例子并不是特别有用,因为函数是在定义后立即被调用的。当响应某种变化或事件(如用户交互)而调用函数时,函数会更有用。

您还可以定义函数,这样就不必显式地创建和分配变量,如清单 4-7 所示。

function myFunc() {

    console.log("This statement is inside the function");
}

console.log("This statement is outside the function");

myFunc();

Listing 4-7Defining a Function in the main.js File in the src Folder

代码的工作方式与清单 4-6 相同,但大多数开发人员更熟悉,这也是我在本书中通常定义函数的方式,因为它非常适合 Vue.js 开发。

使用现代 JavaScript 特性

近年来,JavaScript 已经现代化,增加了方便的语言特性,并对常见任务(如数组处理)可用的实用函数进行了大量扩展。并不是所有的浏览器都支持最新的特性,因此 Vue.js 开发工具包括 Babel 包,它负责将使用最新特性编写的 JavaScript 转换为可以依赖于在大多数主流浏览器中工作的代码。这意味着您能够享受现代开发体验,而无需关注处理浏览器之间的差异和跟踪每个浏览器支持的功能。这种翻译是特定于 JavaScript 语言的,它没有扩展到您可能想在应用中使用的更广泛的 API,比如我在第 1 章中使用的本地存储特性。对于这种类型的特性,您需要考虑您的应用必须到达的浏览器,以及您想要的 API 是否可用。一个很好的起点是caniuse.com,它将让您深入了解对一组广泛的 API 和相关特性的支持级别。

用参数定义函数

JavaScript 允许您为函数定义参数,如清单 4-8 所示。

function myFunc(name, weather) {
    console.log("Hello" + name + ".");
    console.log("It is" + weather + "today.");
}

myFunc("Adam", "sunny");

Listing 4-8Defining Functions with Parameters in the main.js File in the src Folder

我给myFunc函数添加了两个参数,称为nameweather。JavaScript 是一种动态类型语言,这意味着在定义函数时不必声明参数的数据类型。当我在本章后面讲述 JavaScript 变量时,我会回到动态类型。要调用带参数的函数,需要在调用函数时提供值作为参数,如下所示:

...
myFunc("Adam", "sunny");
...

该清单的结果如下:

Hello Adam.
It is sunny today.

使用默认和 Rest 参数

调用函数时提供的参数数量不需要与函数中的参数数量相匹配。如果调用函数时使用的参数少于它拥有的参数,那么任何没有提供值的参数的值都是undefined,这是一个特殊的 JavaScript 值。如果调用函数时使用的参数多于实际参数,那么多余的参数将被忽略。

这样做的结果是,您不能创建两个具有相同名称和不同参数的函数,并期望 JavaScript 根据您在调用函数时提供的参数来区分它们。这被称为多态性,尽管它在 Java 和 C#等语言中受支持,但在 JavaScript 中不可用。相反,如果您定义了两个同名的函数,那么第二个定义将替换第一个定义。

有两种方法可以修改函数,以响应函数定义的参数数量和用于调用函数的参数数量之间的不匹配。默认参数处理实参比参数少的情况,它们允许你为没有实参的参数提供默认值,如清单 4-9 所示。

function myFunc(name, weather = "raining") {

    console.log("Hello" + name + ".");
    console.log("It is" + weather + "today.");
}

myFunc("Adam");

Listing 4-9Using a Default Parameter in the main.js File in the src Folder

函数中的weather参数已被赋予默认值raining,如果仅使用一个参数调用该函数,将使用该值,产生以下结果:

Hello Adam.
It is raining today.

Rest 参数用于在用附加参数调用函数时捕获任何附加参数,如清单 4-10 所示。

function myFunc(name, weather, ...extraArgs) {

    console.log("Hello" + name + ".");
    console.log("It is" + weather + "today.");
    for (let i = 0; i < extraArgs.length; i++) {

        console.log("Extra Arg:" + extraArgs[i]);

    }

}

myFunc("Adam", "sunny", "one", "two", "three");

Listing 4-10Using a Rest Parameter in the main.js File in the src Folder

rest 参数必须是函数定义的最后一个参数,其名称以省略号为前缀(三个句点,...)。rest 参数是一个数组,任何额外的参数都将被赋给它。在清单中,该函数将每个额外的参数打印到控制台,产生以下结果:

Hello Adam.
It is sunny today.
Extra Arg: one
Extra Arg: two
Extra Arg: three

定义返回结果的函数

您可以使用return关键字从函数中返回结果。清单 4-11 显示了一个返回结果的函数。

function myFunc(name) {

    return ("Hello" + name + ".");

}

console.log(myFunc("Adam"));

Listing 4-11Returning a Result from a Function in the main.js File in the src Folder

这个函数定义了一个参数,并用它来产生一个结果。我调用函数并将结果作为参数传递给console.log函数,如下所示:

...
console.log(myFunc("Adam"));
...

请注意,您不必声明该函数将返回一个结果或表示结果的数据类型。该清单的结果如下:

Hello Adam.

将函数用作其他函数的参数

JavaScript 函数可以被视为对象,这意味着您可以使用一个函数作为另一个函数的参数,如清单 4-12 所示。

function myFunc(nameFunction) {

    return ("Hello" + nameFunction() + ".");

}

console.log(myFunc(function () {

    return "Adam";

}));

Listing 4-12Using a Function as an Arguments in the main.js File in the src Folder

myFunc函数定义了一个名为nameFunction的参数,它调用这个参数来获取插入到它返回的字符串中的值。我将一个返回Adam作为参数的函数传递给myFunc,它产生以下输出:

Hello Adam.

函数可以链接在一起,从小而容易测试的代码片段中构建更复杂的功能,如清单 4-13 所示。

function myFunc(nameFunction) {
    return ("Hello" + nameFunction() + ".");
}

function printName(nameFunction, printFunction) {

    printFunction(myFunc(nameFunction));

}

printName(function () { return "Adam" }, console.log);

Listing 4-13Chaining Functions Calls in the main.js File in the src Folder

此示例产生以下输出:

Hello Adam.

使用箭头功能

箭头函数——也称为胖箭头函数λ表达式——是定义函数的另一种方式,通常用于定义仅用作其他函数参数的函数。清单 4-14 用箭头函数替换了前面例子中的函数。

const myFunc = (nameFunction) => ("Hello" + nameFunction() + ".");

const printName = (nameFunction, printFunction) =>

    printFunction(myFunc(nameFunction));

printName(function () { return "Adam" }, console.log);

Listing 4-14Using Arrow Functions in the main.js File in the src Folder

这些函数与清单 4-13 中的函数执行相同的工作。箭头函数有三个部分:输入参数、等号和大于号(“箭头”),最后是函数结果。只有当 arrow 函数需要执行多条语句时,才需要关键字return和花括号。在这一章的后面有更多的箭头函数的例子,你会在整本书中看到它们的使用。

警告

箭头功能不能用于所有 Vue.js 功能。请密切注意后面章节中的示例,并使用所示的函数类型。

使用变量和类型

let关键字用于声明变量,也可以在一条语句中为变量赋值——与我在前面的例子中使用的const关键字相反,它创建一个不可修改的常量值。

当您使用letconst时,您创建的变量或常量只能在定义它们的代码区域中被访问,这被称为变量或常量的范围,如清单 4-15 所示。

function messageFunction(name, weather) {

    let message = "Hello, Adam";

    if (weather == "sunny") {

        let message = "It is a nice day";

        console.log(message);

    } else {

        let message = "It is" + weather + "today";

        console.log(message);

    }

    console.log(message);

}

messageFunction("Adam", "raining");

Listing 4-15Using let to Declare Variables in the main.js File in the src Folder

在这个例子中,有三个语句使用let关键字来定义一个名为message的变量。每个变量的范围限于定义它的代码区域,产生以下结果:

It is raining today
Hello, Adam

这似乎是一个奇怪的例子,但是还有另一个关键字可以用来声明变量:varletconst关键字是 JavaScript 规范中相对较新的补充,旨在解决var行为方式中的一些奇怪之处。清单 4-16 以清单 4-15 为例,将let替换为var

使用 Let 和 Const

对于您不希望更改的任何值,使用const关键字是一个很好的实践,这样,如果试图进行任何修改,您都会收到一个错误。然而,这是我很少遵循的一种做法——一部分是因为我仍然在努力适应不使用var关键字,另一部分是因为我用一系列语言编写代码,并且有一些我避免的功能,因为当我从一种语言切换到另一种语言时它们会绊倒我。如果你是 JavaScript 新手,那么我建议你试着正确使用constlet,避免步我后尘。

function messageFunction(name, weather) {
    var message = "Hello, Adam";

    if (weather == "sunny") {
        var message = "It is a nice day";

        console.log(message);
    } else {
        var message = "It is" + weather + "today";

        console.log(message);
    }
    console.log(message);
}

messageFunction("Adam", "raining");

Listing 4-16Using var to Declare Variables in the main.js File in the src Folder

当您保存列表中的更改时,您将看到以下结果:

It is raining today
It is raining today

问题是var关键字创建的变量的作用域是包含函数,这意味着所有对message的引用都是指同一个变量。这甚至会给有经验的 JavaScript 开发人员带来意想不到的结果,这也是引入更传统的let关键字的原因。

使用可变闭包

如果你在另一个函数内部定义一个函数——创建内部外部函数——那么内部函数能够访问外部函数的变量,使用一个叫做闭包的特性,就像这样:

function myFunc(name) {
    let myLocalVar = "sunny";
    let innerFunction = function () {

        return ("Hello" + name + ". Today is" + myLocalVar + ".");

    }

    return innerFunction();

}

console.log(myFunc("Adam"));

这个例子中的内部函数能够访问外部函数的局部变量,包括它的参数。这是一个强大的特性,意味着您不必在内部函数上定义参数来传递数据值,但是需要小心,因为当使用像counterindex这样的普通变量名时,很容易得到意外的结果,您可能没有意识到您正在重用外部函数中的变量名。

使用基本类型

JavaScript 定义了一组基本的原语类型:stringnumberboolean。这似乎是一个很短的列表,但是 JavaScript 设法将很多灵活性融入到这些类型中。

小费

我在这里简化。您可能会遇到另外三种原语。已经声明但没有赋值的变量是undefined,而null值用来表示一个变量没有值,就像其他语言一样。最后一个原语类型是Symbol,它是一个不可变的值,表示一个惟一的 ID,但是在编写本文时还没有广泛使用。

使用布尔值

boolean类型有两个值:truefalse。清单 4-17 显示了正在使用的两个值,但是这种类型在条件语句中使用时最有用,比如一个if语句。该清单中没有控制台输出。

let firstBool = true;
let secondBool = false;

Listing 4-17Defining boolean Values in the main.js File in the src Folder

使用字符串

您可以使用双引号或单引号字符来定义string值,如清单 4-18 所示。

let firstString = "This is a string";
let secondString = 'And so is this';

Listing 4-18Defining string Variables in the main.js File in the src Folder

您使用的引号字符必须匹配。例如,你不能用单引号开始一个字符串,然后用双引号结束。此列表没有控制台输出。JavaScript 为string对象提供了一组基本的属性和方法,其中最有用的在表 4-2 中有描述。

表 4-2

有用的字符串属性和方法

|

名字

|

描述

| | --- | --- | | length | 此属性返回字符串中的字符数。 | | charAt(index) | 此方法返回包含指定索引处的字符的字符串。 | | concat(string) | 此方法返回一个新字符串,该字符串将调用该方法的字符串和作为参数提供的字符串连接在一起。 | | indexOf(term, start) | 该方法返回第一个索引,在该索引处term出现在字符串中,如果没有匹配,则返回-1。可选的start参数指定搜索的起始索引。 | | replace(term, newTerm) | 该方法返回一个新字符串,其中所有的term实例都被替换为newTerm。 | | slice(start, end) | 此方法返回包含起始和结束索引之间的字符的子字符串。 | | split(term) | 这个方法将一个字符串分割成一个由term分隔的值数组。 | | toUpperCase()``toLowerCase() | 这些方法返回所有字符都是大写或小写的新字符串。 | | trim() | 此方法返回一个新字符串,其中所有的前导和尾随空白字符都已被删除。 |

使用模板字符串

一个常见的编程任务是将静态内容与数据值结合起来,以生成可以呈现给用户的字符串。传统的方法是通过字符串连接,这是我在本章的例子中一直使用的方法,如下所示:

...
let message = "It is" + weather + "today";
...

JavaScript 还支持模板字符串,它允许内联指定数据值,这有助于减少错误,带来更自然的开发体验。清单 4-19 展示了模板字符串的使用。

function messageFunction(weather) {
    let message = `It is ${weather} today`;
    console.log(message);
}

messageFunction("raining");

Listing 4-19Using a Template String in the main.js File in the src Folder

模板字符串以反斜杠(``js 字符)开始和结束,数据值由花括号表示,前面有一个美元符号。例如,这个字符串将变量weather`的值合并到模板字符串中:

...
let message = `It is ${weather} today`;
...

```js

此示例产生以下输出:

It is raining today

#### 使用数字

`number`类型用于表示*整数*和*浮点*(也称为*实数*)。清单 [4-20](#PC38) 提供了一个演示。

let daysInWeek = 7; let pi = 3.14; let hexValue = 0xFFFF;

Listing 4-20Defining number Values in the main.js File in the src Folder

您不必指定使用哪种号码。您只需表达您需要的值,JavaScript 就会相应地执行。在清单中,我定义了一个整数值、一个浮点值,并在一个值前面加上了`0x`来表示一个十六进制值。

## 使用 JavaScript 运算符

JavaScript 定义了一组非常标准的操作符。我在表 [4-3](#Tab3) 中总结了最有用的。

表 4-3

有用的 JavaScript 运算符

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"></colgroup> 
| 

操作员

 | 

描述

 |
| --- | --- |
| `++, --` | 前或后递增和递减 |
| `+, -, *, /, %` | 加法、减法、乘法、除法、余数 |
| `<, <=, >, >=` | 小于,小于等于,大于,大于等于 |
| `==, !=` | 平等和不平等测试 |
| `===, !==` | 同一性和非同一性测试 |
| `&&, &#124;&#124;` | 逻辑 AND 和 OR (&#124;&#124;用于合并空值) |
| `=` | 分配 |
| `+` | 串并置 |
| `?:` | 三操作数条件语句 |

### 使用条件语句

许多 JavaScript 操作符与条件语句一起使用。在本书中,我倾向于使用`if/else`和`switch`语句。清单 [4-21](#PC39) 展示了两者的用法,这对大多数开发者来说都是熟悉的。

let name = "Adam";

if (name == "Adam") { console.log("Name is Adam"); } else if (name == "Jacqui") { console.log("Name is Jacqui"); } else { console.log("Name is neither Adam or Jacqui"); }

switch (name) { case "Adam": console.log("Name is Adam"); break; case "Jacqui": console.log("Name is Jacqui"); break; default: console.log("Name is neither Adam or Jacqui"); break; }

Listing 4-21Using Conditional Statements in the main.js File in the src Folder

此示例产生以下结果:

Name is Adam Name is Adam

### 相等运算符与相同运算符

等式和等式运算符特别值得注意。相等运算符将尝试将操作数强制(转换)为相同的类型来评估相等性。这是一个方便的特性,只要你意识到它正在发生。清单 [4-22](#PC41) 展示了等式操作符的作用。

let firstVal = 5; let secondVal = "5";

if (firstVal == secondVal) { console.log("They are the same"); } else { console.log("They are NOT the same"); }

Listing 4-22Using the Equality Operator in the main.js File in the src Folder

该示例的输出如下:

They are the same

JavaScript 将两个操作数转换成相同的类型,并对它们进行比较。本质上,相等运算符测试值是否相同,而不管它们的类型如何。如果你想测试确保值*和*的类型是相同的,那么你需要使用恒等运算符(`===`,三个等号,而不是两个等号的运算符),如清单 [4-23](#PC43) 所示。

let firstVal = 5; let secondVal = "5";

if (firstVal === secondVal) {

console.log("They are the same");

} else { console.log("They are NOT the same"); }

Listing 4-23Using the Identity Operator in the main.js File in the src Folder

在本例中,identity 运算符将认为这两个变量是不同的。该运算符不强制类型。结果如下:

They are NOT the same

### 显式转换类型

字符串连接操作符(`+`)优先于加法操作符(还有`+`),这意味着 JavaScript 将优先于加法连接变量。这可能会造成混乱,因为 JavaScript 也会自由地转换类型以产生结果——而不总是预期的结果,如清单 [4-24](#PC45) 所示。

let myData1 = 5 + 5;

let myData2 = 5 + "5";

console.log("Result 1:" + myData1); console.log("Result 2:" + myData2);

Listing 4-24String Concatentation Operator Precedence in the main.js File in the src Folder

这些语句会产生以下结果:

Result 1: 10 Result 2: 55

第二种结果是引起混乱的那种。通过运算符优先级和过急类型转换的组合,原本应该是加法运算的操作被解释为字符串串联。为了避免这种情况,可以显式转换值的类型,以确保执行正确的操作,如以下部分所述。

#### 将数字转换为字符串

如果您正在处理多个数字变量,并希望将它们连接成字符串,那么您可以使用`toString`方法将数字转换成字符串,如清单 [4-25](#PC47) 所示。

let myData1 = (5).toString() + String(5);

console.log("Result:" + myData1);

Listing 4-25Using the number.toString Method in the main.js File in the src Folder

注意,我将数值放在括号中,然后调用了`toString`方法。这是因为在调用`number`类型定义的方法之前,您必须允许 JavaScript 将文字值转换成`number`。我还展示了实现相同效果的另一种方法,即调用`String`函数,并将数值作为参数传入。这两种技术具有相同的效果,都是将一个`number`转换成一个`string`,这意味着`+`操作符用于字符串连接而不是加法。该脚本的输出如下:

Result: 55

还有一些其他的方法可以让你更好地控制一个数字如何被表示成一个字符串。我在表 [4-4](#Tab4) 中简要描述了这些方法。表格中显示的所有方法都由`number`类型定义。

表 4-4

有用的数字到字符串的方法

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"></colgroup> 
| 

方法

 | 

描述

 |
| --- | --- |
| `toString()` | 此方法返回一个表示以 10 为基数的数字的字符串。 |
| `toString(2)``toString(8)``toString(16)` | 此方法返回以二进制、八进制或十六进制表示法表示数字的字符串。 |
| `toFixed(n)` | 该方法返回一个表示小数点后有`n`位的实数的字符串。 |
| `toExponential(n)` | 该方法返回一个字符串,该字符串使用指数表示法表示一个数字,小数点前有一位数字,小数点后有`n`位数字。 |
| `toPrecision(n)` | 该方法返回一个字符串,该字符串表示一个具有`n`个有效数字的数字,如果需要,可以使用指数符号。 |

#### 将字符串转换为数字

补充技术是将字符串转换为数字,这样您就可以执行加法而不是连接。你可以用`Number`函数来实现,如清单 [4-26](#PC49) 所示。

let firstVal = "5"; let secondVal = "5";

let result = Number(firstVal) + Number(secondVal); console.log("Result:" + result);

Listing 4-26Converting Strings to Numbers in the main.js File in the src Folder

该脚本的输出如下:

Result: 10

`Number`函数解析字符串值的方式很严格,但是您可以使用另外两个更灵活的函数,它们会忽略后面的非数字字符。这些功能是`parseInt`和`parseFloat`。我已经在表 [4-5](#Tab5) 中描述了所有三种方法。

表 4-5

对数字方法有用的字符串

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"></colgroup> 
| 

方法

 | 

描述

 |
| --- | --- |
| `Number(str)` | 此方法分析指定的字符串以创建整数或实数值。 |
| `parseInt(str)` | 此方法分析指定的字符串以创建整数值。 |
| `parseFloat(str)` | 此方法分析指定的字符串以创建整数或实数值。 |

## 使用数组

JavaScript 数组的工作方式类似于大多数其他编程语言中的数组。清单 [4-27](#PC51) 展示了如何创建和填充一个数组。

let myArray = new Array();

myArray[0] = 100;

myArray[1] = "Adam";

myArray[2] = true;

Listing 4-27Creating and Populating an Array in the main.js File in the src Folder

我通过调用`new Array()`创建了一个新数组。这创建了一个空数组,我将它赋给了变量`myArray`。在随后的语句中,我为数组中的不同索引位置赋值。(这个清单没有输出。)

在这个例子中有一些事情需要注意。首先,在创建数组时,我不需要声明数组中的项数。JavaScript 数组会自动调整大小以容纳任意数量的项目。第二点是,我不必声明数组将保存的数据类型。任何 JavaScript 数组都可以包含任何混合的数据类型。在这个例子中,我给数组分配了三个项目:一个`number`、一个`string`和一个`boolean`。

### 使用数组文本

array literal 样式允许您在一条语句中创建和填充一个数组,如清单 [4-28](#PC52) 所示。

let myArray = [100, "Adam", true];

Listing 4-28Using the Array Literal Style in the main.js File in the src Folder

在这个例子中,我通过在方括号(`[`和`]`)之间指定我想要的数组中的项目,指定了应该给`myArray`变量分配一个新的数组。(这个清单中没有控制台输出。)

### 读取和修改数组的内容

使用方括号(`[`和`]`)读取给定索引处的值,将所需的索引放在括号之间,如清单 [4-29](#PC53) 所示。

let myArray = [100, "Adam", true];

console.log(Index 0: ${myArray[0]});

Listing 4-29Reading the Data from an Array Index in the main.js File in the src Folder

只需给索引赋值,就可以修改 JavaScript 数组中任何位置的数据。就像常规变量一样,您可以在索引处切换数据类型,不会有任何问题。清单的输出如下所示:

Index 0: 100

清单 4-30 展示了如何修改一个数组的内容。

let myArray = [100, "Adam", true];

myArray[0] = "Tuesday";

console.log(Index 0: ${myArray[0]});

Listing 4-30Modifying the Contents of an Array in the main.js File in the src Folder

在这个例子中,我将一个`string`赋值给数组中的位置`0`,这个位置以前是由一个`number`持有的,并产生以下输出:

Index 0: Tuesday

### 枚举数组的内容

使用一个`for`循环或者使用`forEach`方法来枚举数组的内容,该方法接收一个被调用来处理数组中每个元素的函数。两种方法如清单 [4-31](#PC57) 所示。

let myArray = [100, "Adam", true];

for (let i = 0; i < myArray.length; i++) { console.log(Index ${i}: ${myArray[i]}); }

console.log("---");

myArray.forEach((value, index) => console.log(Index ${index}: ${value}));

Listing 4-31Enumerating the Contents of an Array in the main.js File in the src Folder

JavaScript `for`循环的工作方式与许多其他语言中的循环一样。使用`length`属性确定数组中有多少个元素。

传递给`forEach`方法的函数有两个参数:要处理的当前项的值和该项在数组中的位置。在这个清单中,我使用了一个 arrow 函数作为`forEach`方法的参数,这是它们擅长的一种用法(您将在本书中看到这种用法)。清单的输出如下所示:

Index 0: 100 Index 1: Adam Index 2: true


Index 0: 100 Index 1: Adam Index 2: true

### 使用扩展运算符

spread 运算符用于扩展数组,以便其内容可以用作函数参数。清单 [4-32](#PC59) 定义了一个函数,该函数接受多个参数,并使用数组中的值调用它,使用或不使用 spread 运算符。

function printItems(numValue, stringValue, boolValue) { console.log(Number: ${numValue}); console.log(String: ${stringValue}); console.log(Boolean: ${boolValue}); }

let myArray = [100, "Adam", true];

printItems(myArray[0], myArray[1], myArray[2]);

printItems(...myArray);

Listing 4-32Using the Spread Operator in the main.js File in the src Folder

spread 操作符是一个省略号(三个句点的序列),它导致数组被解包并作为单独的参数传递给`printItems`函数。

... printItems(...myArray); ...

spread 操作符还可以很容易地将数组连接在一起,如清单 [4-33](#PC61) 所示。

let myArray = [100, "Adam", true]; let myOtherArray = [200, "Bob", false, ...myArray];

myOtherArray.forEach((value, index) => console.log(Index ${index}: ${value}));

Listing 4-33Concatenating Arrays in the main.js File in the src Folder

使用 spread 操作符,我可以在定义`myOtherArray`时将`myArray`指定为一个项,结果是第一个数组的内容将被解包并作为项添加到第二个数组中。此示例产生以下结果:

Index 0: 200 Index 1: Bob Index 2: false Index 3: 100 Index 4: Adam Index 5: true

### 使用内置数组方法

JavaScript `Array`对象定义了许多可以用来处理数组的方法,表 [4-6](#Tab6) 中描述了其中最有用的方法。

表 4-6

有用的数组方法

<colgroup><col class="tcol1 align-left"> <col class="tcol2 align-left"></colgroup> 
| 

方法

 | 

描述

 |
| --- | --- |
| `concat(otherArray)` | 此方法返回一个新数组,该数组将调用它的数组与指定为参数的数组连接起来。可以指定多个数组。 |
| `join(separator)` | 该方法将数组中的所有元素连接起来形成一个字符串。该参数指定用于分隔各项的字符。 |
| `pop()` | 此方法移除并返回数组中的最后一项。 |
| `shift()` | 此方法移除并返回数组中的第一个元素。 |
| `push(item)` | 此方法将指定的项追加到数组的末尾。 |
| `unshift(item)` | 此方法在数组的开头插入一个新项。 |
| `reverse()` | 此方法返回一个新数组,该数组包含逆序排列的项。 |
| `slice(start,end)` | 此方法返回数组的一部分。 |
| `sort()` | 此方法对数组进行排序。可选的比较功能可用于执行自定义比较。 |
| `splice(index, count)` | 该方法从指定的`index`开始,从数组中移除`count`项。移除的项作为方法的结果返回。 |
| `unshift(item)` | 此方法在数组的开头插入一个新项。 |
| `every(test)` | 该方法为数组中的每一项调用`test`函数,如果函数为所有项返回`true`,则返回`true`,否则返回 false。 |
| `some(test)` | 如果为数组中的每一项调用`test`函数至少返回一次`true`,则该方法返回`true`。 |
| `filter(test)` | 该方法返回一个新数组,其中包含了`test`函数返回的`true`项。 |
| `find(test)` | 该方法返回数组中第一个项目,对于该项目,`test`函数返回`true`。 |
| `findIndex(test)` | 该方法返回数组中第一项的索引,对于该数组,`test`函数返回`true`。 |
| `forEach(callback)` | 这个方法为数组中的每一项调用`callback`函数,如前一节所述。 |
| `includes(value)` | 如果数组包含指定的值,这个方法返回`true`。 |
| `map(callback)` | 该方法返回一个新数组,其中包含为数组中的每一项调用`callback`函数的结果。 |
| `reduce(callback)` | 该方法返回通过调用回调函数为数组中的每一项生成的累计值。 |

由于表 [4-6](#Tab6) 中的许多方法返回一个新数组,这些方法可以链接在一起处理数据,如清单 [4-34](#PC63) 所示。

let products = [ { name: "Hat", price: 24.5, stock: 10 }, { name: "Kayak", price: 289.99, stock: 1 }, { name: "Soccer Ball", price: 10, stock: 0 }, { name: "Running Shoes", price: 116.50, stock: 20 } ];

let totalValue = products .filter(item => item.stock > 0) .reduce((prev, item) => prev + (item.price * item.stock), 0);

console.log(Total value: $${totalValue.toFixed(2)});

Listing 4-34Processing an Array in the main.js File in the src Folder

我使用`filter`方法选择数组中`stock`值大于零的项目,并使用`reduce`方法确定这些项目的总值,产生以下输出:

Total value: $2864.99

## 使用对象

有几种方法可以在 JavaScript 中创建对象。清单 [4-35](#PC65) 给出了一个简单的例子。

let myData = new Object(); myData.name = "Adam"; myData.weather = "sunny";

console.log(Hello ${myData.name}.); console.log(Today is ${myData.weather}.);

Listing 4-35Creating an Object in the main.js File in the src Folder

我通过调用`new Object()`创建一个对象,并将结果(新创建的对象)赋给一个名为`myData`的变量。一旦创建了对象,我就可以通过赋值来定义对象的属性,就像这样:

...

myData.name = "Adam";

...

在这个语句之前,我的对象没有名为`name`的属性。当语句执行后,该属性确实存在,并被赋予了值`Adam`。您可以通过将变量名和属性名与句点组合来读取属性值,如下所示:

... console.log(Hello ${myData.name}.); ...

清单的结果如下:

Hello Adam. Today is sunny.

### 使用对象文字

您可以使用对象文字格式在一个步骤中定义一个对象及其属性,如清单 [4-36](#PC69) 所示。

let myData = {

name: "Adam",

weather: "sunny"

};

console.log(Hello ${myData.name}.); console.log(Today is ${myData.weather}.);

Listing 4-36Using the Object Literal Format in the main.js File in the src Folder

使用冒号(`:`)将您要定义的每个属性与其值分开,使用逗号(`,`)将属性分开。效果与前面的示例相同,清单的结果如下:

Hello Adam. Today is sunny.

#### 使用变量作为对象属性

如果使用变量作为对象属性,JavaScript 将使用变量名作为属性名,变量值作为属性值,如清单 [4-37](#PC71) 所示。

let name = "Adam"

let myData = { name,

weather: "sunny" };

console.log(Hello ${myData.name}.); console.log(Today is ${myData.weather}.);

Listing 4-37Using a Variable in an Object Literal in the main.js File in the src Folder

`name`变量用于给`myData`对象添加一个属性;该房产名为`name`,其价值为`Adam`。当您想要将一组数据值组合成一个对象时,这是一种有用的技术,您将在后面章节的示例中看到它的使用。清单 [4-37](#PC71) 中的代码产生以下输出:

Hello Adam. Today is sunny.

### 将函数用作方法

我最喜欢 JavaScript 的一个特性是可以向对象添加函数。定义在对象上的函数被称为*方法*。清单 [4-38](#PC73) 展示了如何以这种方式添加方法。

let myData = { name: "Adam", weather: "sunny", printMessages: function () {

    console.log(`Hello ${myData.name}.`);

    console.log(`Today is ${myData.weather}.`);

}

};

myData.printMessages();

Listing 4-38Adding Methods to an Object in the main.js File in the src Folder

在这个例子中,我使用了一个函数来创建一个名为`printMessages`的方法。注意,为了引用对象定义的属性,我必须使用`this`关键字。当一个函数作为一个方法使用时,该函数通过特殊变量`this`被隐式传递给调用该方法的对象作为参数。清单的输出如下所示:

Hello Adam. Today is sunny.

您也可以不使用`function`关键字来定义方法,如清单 [4-39](#PC75) 所示。

let myData = { name: "Adam", weather: "sunny", printMessages() {

    console.log(`Hello ${myData.name}.`);

    console.log(`Today is ${myData.weather}.`);

}

};

myData.printMessages();

Listing 4-39Defining a Method in the main.js File in the src Folder

至少在我看来,这是一种更自然的定义方法的方式,我在本书的许多例子中都使用了这种方法。该清单的输出如下:

Hello Adam. Today is sunny.

### 将属性从一个对象复制到另一个对象

在后面章节的例子中,我需要将所有属性从一个对象复制到另一个对象,以演示不同的 Vue.js 特性。JavaScript 为此提供了`Object.assign`方法,如清单 [4-40](#PC77) 所示。

let myData = { name: "Adam", weather: "sunny", printMessages() { console.log(Hello ${myData.name}.); console.log(Today is ${myData.weather}.); } };

let secondObject = {};

Object.assign(secondObject, myData);

secondObject.printMessages();

Listing 4-40Copying Object Properties in the main.js File in the src Folder

这个示例创建一个没有属性的新对象,并使用`Object.assign`方法从`myData`对象中复制属性及其值。此示例产生以下输出:

Hello Adam. Today is sunny.

## 理解 JavaScript 模块

前一章中的例子包含在一个 JavaScript 文件中。这对于简单的例子来说没问题,但是复杂的 web 应用可能包含大量的代码和内容,这是不可能在一个文件中管理的。

为了将应用分成更易管理的块,JavaScript 支持*模块*,其中包含应用其他部分所依赖的 JavaScript 代码。在接下来的部分中,我将解释定义和使用模块的不同方式。

### 创建和使用简单的 JavaScript 模块

模块通常在它们自己的文件夹中定义,所以我创建了`src/maths`文件夹,并在其中添加了一个名为`sum.js`的文件,内容如清单 [4-41](#PC79) 所示。

export default function(values) { return values.reduce((total, val) => total + val, 0); }

Listing 4-41The Contents of the sum.js File in the src/maths Folder

`sum.js`文件包含一个函数,该函数接受一组值,并使用 JavaScript array `reduce`方法对它们求和并返回结果。这个例子的重要之处不在于它做了什么,而在于函数是在自己的文件中定义的,这是模块的基本构造块。

清单 [4-41](#PC79) 中使用了两个你在定义模块时会经常遇到的关键字。`export`关键字用于表示模块外部可用的特性。默认情况下,JavaScript 文件的内容是私有的,必须使用关键字`export`显式共享,然后才能在应用的其余部分使用。当模块包含单个特性时,使用`default`关键字,例如清单 [4-41](#PC79) 中使用的函数。`export`和`default`关键字一起用于指定`sum.js`文件中的唯一函数可用于应用的其余部分。

#### 使用简单的 JavaScript 模块

使用模块需要另一个关键字:`import`关键字。在清单 [4-42](#PC80) 中,我使用了`import`关键字来访问上一节中定义的函数,以便它可以在`main.js`文件中使用。

import additionFunction from "./maths/sum";

let values = [10, 20, 30, 40, 50];

let total = additionFunction(values);

console.log(Total: ${total});

Listing 4-42Using a Simple JavaScript Module in the main.js File in the src Folder

`import`关键字用于声明对模块的依赖。`import`关键字可以有多种用法,但这是您在处理自己创建的模块时最常使用的格式。图 [4-3](#Fig3) 示出了关键零件。

![img/465686_1_En_4_Fig3_HTML.jpg](img/465686_1_En_4_Fig3_HTML.jpg)

图 4-3

声明对模块的依赖

`import`关键字后面是一个标识符,它是函数被使用时的名字;这个例子中的标识符是`additionFunction`。

### 小费

请注意,应用标识符的是`import`语句,这意味着使用模块中函数的代码选择它将被识别的名称,并且应用不同部分中同一模块的多个`import`语句可以使用不同的名称来引用同一函数。

`from`关键字跟在标识符后面,然后是模块的位置。密切关注位置很重要,因为不同的位置格式会产生不同的行为,如侧栏中所述。

在构建过程中,Vue.js 工具将检测到`import`语句,并将`sum.js`文件中的函数包含在发送给浏览器的 JavaScript 文件中,以便浏览器可以执行应用。在`import`语句中使用的标识符可以用来访问模块中的函数,就像使用本地定义的函数一样。

... let total = additionFunction(values); ...

如果您检查浏览器的 JavaScript 控制台,您会看到清单 [4-42](#PC80) 中的代码使用该模块的函数产生以下结果:

Total: 150

### 了解模块位置

模块的位置改变了构建工具在创建发送到浏览器的 JavaScript 文件时查找模块的方式。对于您自己定义的模块,位置被指定为相对路径,以一个或两个句点开始,表示该路径相对于当前文件或当前文件的父目录。在清单 [4-42](#PC80) 中,位置以句点开始。

... import additionFunction from "./maths/sum"; ...

这个位置告诉构建工具在`sum`模块上有一个依赖项,这个模块可以在`maths`文件夹中找到,这个文件夹与包含导入语句的文件在同一个目录中(注意文件扩展名没有包含在这个位置中)。

指定相对于当前文件的路径的另一种方法是相对于项目文件夹导航,如下所示:

... import additionFunction from "@/maths/sum"; ...

当位置以`@`字符为前缀时,模块相对于`src`文件夹定位。

如果您省略了初始句点和`@`字符,那么`import`语句声明了对`node_modules`文件夹中的一个模块的依赖,该文件夹是在项目设置期间安装的包的安装位置。这种位置用于访问第三方包提供的功能,包括 Vue.js 包,这就是为什么您会在 Vue.js 项目中看到这样的语句:

... import Vue from "vue"; ...

这个`import`语句的位置不是以句点开始的,它将被解释为对项目的`node_modules`文件夹中的`vue`模块的依赖,该文件夹是提供核心 Vue.js 应用特性的包。

### 在模块中定义多个特征

模块可以包含一个以上的函数或值,这对相关特性的分组很有用。为了演示,我在`src/maths`文件夹中创建了一个名为`operations.js`的文件,并添加了清单 [4-43](#PC86) 中所示的代码。

export function multiply(values) { return values.reduce((total, val) => total * val, 1); }

export function subtract(amount, values) { return values.reduce((total, val) => total - val, amount); }

export function divide(first, second) { return first / second; }

Listing 4-43The Contents of the operations.js File in the src/maths Folder

该模块定义了三个应用了关键字`export`的函数。与前面的例子不同,没有使用`default`关键字,每个函数都有自己的名称。当使用包含多个功能的模块时,需要不同的方法,如清单 [4-44](#PC87) 所示。

import additionFunction from "./maths/sum";

import { multiply, subtract } from "./maths/operations";

let values = [10, 20, 30, 40, 50];

console.log(Sum: ${additionFunction(values)});

console.log(Multiply: ${multiply(values)});

console.log(Subtract: ${subtract(1000, values)});

Listing 4-44Using a Module in the main.js File in the src Folder

`import`关键字后面的括号包围了我要使用的函数列表,在本例中是用逗号分隔的`multiply`和`subtract`函数。我只声明对我需要的函数的依赖,对`divide`函数没有依赖,它在模块中定义,但在清单 [4-44](#PC87) 中没有使用。此示例产生以下输出:

Sum: 150 Multiply: 12000000 Subtract: 850

#### 更改模块功能名称

这种方法的一个不同之处是,函数使用的名称现在是由模块定义的,而不是由使用函数的代码定义的。如果您不想使用模块提供的名称,那么您可以使用`as`关键字指定一个名称,如清单 [4-45](#PC89) 所示。

import additionFunction from "./maths/sum";

import { multiply, subtract as minus } from "./maths/operations";

let values = [10, 20, 30, 40, 50];

console.log(Sum: ${additionFunction(values)}); console.log(Multiply: ${multiply(values)});

console.log(Subtract: ${minus(1000, values)});

Listing 4-45Using a Module Alias in the main.js File in the src Folder

我使用了`as`关键字来指定`subtract`函数在导入到`main.js`文件时应该被命名为`minus`。该清单产生与清单 [4-44](#PC87) 相同的输出。

#### 导入整个模块

列出一个模块中所有函数的名称对于复杂的模块来说是无法控制的。一种更优雅的方法是导入一个模块提供的所有特性,并只使用您需要的特性,如清单 [4-46](#PC90) 所示。

import additionFunction from "./maths/sum";

import * as ops from "./maths/operations";

let values = [10, 20, 30, 40, 50];

console.log(Sum: ${additionFunction(values)});

console.log(Multiply: ${ops.multiply(values)});

console.log(Subtract: ${ops.subtract(1000, values)});

Listing 4-46Importing an Entire Module in the main.js File in the src Folder

星号用于导入模块中的所有内容,后跟关键字`as`和一个标识符,通过它可以访问模块函数和值。在这种情况下,标识符是`ops`,这意味着`multiply`、`subtract`和`divide`功能可以作为`ops.multiply`、`ops.subtract`和`ops.divide`来访问。该清单产生与清单 [4-44](#PC87) 相同的输出。

### 在一个模块中组合多个文件

模块可以跨越多个文件,并通过定义一个`index.js`文件来组合,该文件集合了模块将提供给应用其余部分的特性。我在`src/maths`文件夹中添加了一个`index.js`文件,代码如清单 [4-47](#PC91) 所示。

import addition from "./sum";

export function mean(values) { return addition(values)/values.length; }

export { addition }; export * from "./operations";

Listing 4-47The Contents of the index.js File in the src/maths Folder

这个文件以与前面的例子相同的方式开始,用一个`import`语句声明对`sum.js`文件中的函数的依赖,这个函数用在名为`mean`的导出函数中。

该语句从`sum.js`文件中导出函数,以便可以在模块外部使用。我不需要为这个函数指定位置,因为它已经被导入了,在这种情况下,函数包含在大括号中。

最后一条语句导出在`operations.js`文件中定义的所有特征,而不需要先导入它们。当您想在模块外部使用特性,但不需要在`index.js`文件中直接使用它们时,这很有用。

使用一个`index.js`文件允许所有的特性在一条语句中被导入到`main.js`文件中,如清单 [4-48](#PC92) 所示。

import * as math from "./maths";

let values = [10, 20, 30, 40, 50];

console.log(Sum: ${math.addition(values)});

console.log(Multiply: ${math.multiply(values)});

console.log(Subtract: ${math.subtract(1000, values)});

console.log(Mean: ${math.mean(values)});

Listing 4-48Importing an Entire Module in the main.js File in the src Folder

`import`的位置没有指定文件,这是一个简化的语句。在这个例子中,所有的模块特性都可以通过`math`标识符来访问,而不需要知道模块中的哪个文件定义了每个特性。此示例产生以下输出:

Sum: 150 Multiply: 12000000 Subtract: 850 Mean: 30

#### 从多文件模块导入单个特征

您不必从一个模块中导入所有的特性,即使它是用几个文件定义的。在清单 [4-49](#PC94) 中,我修改了`import`语句,以便只导入示例中使用的函数,并且我使用了`as`关键字来演示函数可以被重命名。

import { addition as add, multiply, subtract, mean as average} from "./maths";

let values = [10, 20, 30, 40, 50];

console.log(Add: ${add(values)});

console.log(Multiply: ${multiply(values)});

console.log(Subtract: ${subtract(1000, values)});

console.log(Average : ${average(values)});

Listing 4-49Importing Specific Features in the main.js File in the src Folder

这个例子结合了前面清单中的特性,从模块中导入四个函数,并重命名其中的两个。此示例产生以下输出:

Add: 150 Multiply: 12000000 Subtract: 850 Average : 30

## 理解 JavaScript 承诺

一个*承诺*是一个将在未来某个时间点完成的后台活动。在本书中,承诺最常见的用法是使用 HTTP 请求来请求数据,这是异步执行的,当从 web 服务器收到响应时会产生一个结果。

### 理解异步操作问题

web 应用的经典异步操作是 HTTP 请求,通常用于获取用户需要的数据和内容。我将很快讨论 HTTP 请求,但是我需要一些更简单的东西来开始,所以我在`maths`模块的`index.js`文件中添加了一个函数来异步执行任务,如清单 [4-50](#PC96) 所示。

import addition from "./sum";

export function mean(values) { return addition(values)/values.length; }

export { addition }; export * from "./operations";

export function asyncAdd(values) {

setTimeout(() => {

    let total = addition(values);

    console.log(`Async Total: ${total}`);

    return total;

}, 500);

}

Listing 4-50Adding a Function in the index.js File in the src/maths Folder

`setTimeout`函数在指定的延迟后异步调用一个函数。在清单中,`asyncAdd`函数接收一个参数,该参数在 500 毫秒的延迟后被传递给`addition`函数,为本章中的示例创建一个不会立即完成的后台操作,并表示更有用的操作,比如发出一个 HTTP 请求。在清单 [4-51](#PC97) 中,我已经更新了`main.js`文件以使用`asyncAdd`函数。

import { asyncAdd } from "./maths";

let values = [10, 20, 30, 40, 50];

let total = asyncAdd(values);

console.log(Main Total: ${total});

Listing 4-51Performing Background Work in the main.js File in the src Folder

这个例子说明的问题是,`asyncAdd`函数的结果直到`main.js`文件中的语句被执行后才产生,这可以在浏览器的 JavaScript 控制台的输出中看到:

Main Total: undefined Async Total: 150

浏览器执行`main.js`文件中的语句,并按照指示调用`asyncAdd`函数。浏览器移动到`main.js`文件中的下一条语句,该语句使用`asyncAdd`提供的结果向控制台写入一条消息——但这发生在异步任务完成之前,这就是为什么输出是`undefined`。异步任务随后完成,但是结果被`main.js`文件使用已经太晚了。

### 使用 JavaScript Promise

为了解决上一节中的问题,我需要一种机制,允许我观察异步任务,以便我可以等待它完成,然后写出结果。这就是 JavaScript promise 的作用,我已经将它应用于清单 [4-52](#PC99) 中的`asyncAdd`函数。

import addition from "./sum";

export function mean(values) { return addition(values)/values.length; }

export { addition }; export * from "./operations";

export function asyncAdd(values) { return new Promise((callback) => {

    setTimeout(() => {
        let total = addition(values);
        console.log(`Async Total: ${total}`);
        callback(total);

    }, 500);
});

}

Listing 4-52Using a Promise in the index.js File in the src/maths Folder

在这个例子中很难解开函数。`new`关键字用于创建一个`Promise`,它接受要观察的函数。观察到的函数提供了一个回调,当异步任务完成时调用该回调,并接受任务的结果作为参数。调用回调函数被称为*解析*承诺。

已经成为`asyncAdd`函数结果的`Promise`对象允许观察异步任务,以便在任务完成时执行后续工作,如清单 [4-53](#PC100) 所示。

import { asyncAdd } from "./maths";

let values = [10, 20, 30, 40, 50];

asyncAdd(values).then(total => console.log(Main Total: ${total}));

Listing 4-53Observing a Promise in the main.js File in the src Folder

`then`方法接受一个函数,该函数将在使用回调时被调用。传递给回调的结果被提供给`then`函数。在这种情况下,这意味着在异步任务完成并产生以下输出之前,总数不会写入浏览器的 JavaScript 控制台:

Async Total: 150 Main Total: 150

### 简化异步代码

JavaScript 提供了两个关键字——`async`和`await`——支持异步操作,而不必直接使用承诺。在清单 [4-54](#PC102) 中,我在`main.js`文件中应用了这些关键字。

### 警告

理解使用`async` / `await`不会改变应用的行为方式是很重要的。操作仍然是异步执行的,直到操作完成,结果才可用。这些关键字只是为了简化异步代码的工作,这样你就不必使用`then`方法了。

import { asyncAdd } from "./maths";

let values = [10, 20, 30, 40, 50];

async function doTask() {

let total = await asyncAdd(values);

console.log(`Main Total: ${total}`);

}

doTask();

Listing 4-54Using async and await in the main.js File in the src Folder

这些关键字只能应用于函数,这就是为什么我在清单中添加了`doTask`函数。`async`关键字告诉 JavaScript 这个函数依赖于需要承诺的功能。在调用返回承诺的函数时使用`await`关键字,其作用是将提供的结果分配给`Promise`对象的回调,然后执行后面的语句,产生以下结果:

Async Total: 150 Main Total: 150

```

摘要

在这一章中,我提供了一个关于 JavaScript 的简单入门,重点放在核心功能上,它将帮助你开始 Vue.js 开发。在下一章中,我将开始构建一个更加复杂和现实的项目,名为 SportsStore。