笑话生成器 - 学习 Web 开发

笑话生成器
=====

* 上一页
* Overview: First steps

本节是一个小测验，要求你运用所学知识制作一个笑话生成器。祝玩的愉快！

|  |  |
| --- | --- |
| 前提： | 请读完本章所有小节的内容后再开始这个测验。 |
| 目标： |
 测试你对变量、数字、操作符、字符串和数组等 Javascript
 基本概念的理解程度。
  |

起点
--

测验开始之前需要下载并保存 index.html、style.css、raw-text.txt。

**备注：** 你还可以用类似 JSBin 或 Glitch 这些在线编辑器来完成测验。你可以把 HTML、CSS 及 JavaScript 代码复制过去。如果你选的工具没有独立的 JavaScript 版面，可以随时在 HTML 页面中添加 `<script>` 元素。

项目简介
----

我们提供了一些原始的 HTML / CSS，以及若干字符串和 JavaScript 函数，还需要你来编写一些 JavaScript 代码让项目运行起来：

* 点击“随机生成笑话”按钮时生成一则笑话。
* 若“生成”按钮按下之前，你在“输入自定义的名字”文字框中输入了一个自定义名字，那么生成的笑话中原有的名字（李雷 / Bob）将被取代。
* 通过选择国家名称的单选按钮来确定界面语言以及笑话中温度和重量的制式。
* 点一次按钮，生成一个新故事。点一次生成一个……

可以尝试操作一下：（别偷看源代码哦！）
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
 body {
 font-family: helvetica, sans-serif;
 width: 350px;
 border: 1px solid;
 padding: 1em;
 }
 label {
 font-weight: bold;
 }
 div {
 padding-bottom: 20px;
 }
 input[type="text"] {
 padding: 5px;
 width: 150px;
 }
 p {
 background: #ffc125;
 color: #5e2612;
 padding: 10px;
 visibility: hidden;
 }
 </style>
  </head>

  <body>
    <div>
      <label for="customname" id="lbl-customname">请输入自定义的名字：</label>
      <input id="customname" type="text" placeholder="李雷" />
    </div>
    <div>
      <label for="metric">公制</label
 ><input id="metric" type="radio" name="measure" value="metric" checked />
      <label for="american">美制</label
 ><input id="american" type="radio" name="measure" value="american" />
    </div>
    <div>
      <button class="randomize">随机生成笑话</button>
    </div>
    <!-- Thanks a lot to Willy Aguirre for his help with the code for this assessment -->
    <p class="story"></p>

    <script>
 const customName = document.getElementById("customname");
 const randomize = document.querySelector(".randomize");
 const story = document.querySelector(".story");

 function randomValueFromArray(array) {
 const random = Math.floor(Math.random() \* array.length);
 return array[random];
 }

 let storyText =
 "今天气温 35 摄氏度，:insertx:出门散步。当走到:inserty:门前时，突然就:insertz:。人们都惊呆了，李雷全程目睹但并没有慌，因为:insertx:是一个 140 公斤的胖子，天气又辣么热。";
 let insertX = ["怪兽威利", "大老爹", "圣诞老人"];
 let insertY = ["肯德基", "迪士尼乐园", "白宫"];
 let insertZ = ["自燃了", "在人行道化成了一坨泥", "变成一只鼻涕虫爬走了"];

 randomize.addEventListener("click", result);

 function result() {
 let newStory = storyText;

 let xItem = randomValueFromArray(insertX);
 let yItem = randomValueFromArray(insertY);
 let zItem = randomValueFromArray(insertZ);

 newStory = newStory.replace(":insertx:", xItem);
 newStory = newStory.replace(":insertx:", xItem);
 newStory = newStory.replace(":inserty:", yItem);
 newStory = newStory.replace(":insertz:", zItem);

 if (customName.value !== "") {
 const name = customName.value;
 newStory = newStory.replace("李雷", name);
 }

 if (document.getElementById("american").checked) {
 const weight = Math.round(140 \* 2.20462) + " 磅";
 const temperature = Math.round((35 \* 9) / 5 + 32) + " 华氏度";
 newStory = newStory.replace("35 摄氏度", temperature);
 newStory = newStory.replace("140 公斤", weight);
 }

 story.textContent = newStory;
 story.style.visibility = "visible";
 }
 </script>
  </body>
</html>
```

**备注：** 点击在线试用汉化版本。有兴趣还可以在本节结束后回来 看看代码。（没兴趣就跳过吧 :）

步骤
--

以下是你的工作。

初始化变量和函数：

1. 将刚下载的文本文件中的“1. 定义变量和函数”标题项下所有代码复制粘贴至 main.js 中。此时你就有了三个变量（`customName` 是对“输入自定义的名字”文本框的引用，`randomize` 是对“随机生成笑话”按钮的引用，`story` 是对 HTML 底部的、准备存放笑话的 `<p>` 元素的引用）和一个函数（`randomValueFromArray()` 取一个数组作参数，随机返回数组中的一个元素）。
2. 然后是文本文件的第二节——“2. 纯文本字符串”。这里包含了一些字符串，这些字符串是项目的输入信息。你应该在 main.js 文件中用变量来保存它们。
    1. 用 `storyText` 变量保存第一个长字符串，“今天气温……”。
    2. 用 `insertX` 数组保存第一组三个字符串，“怪兽威利……”。
    3. 用 `insertY` 数组保存第二组三个字符串。“肯德基……”。
    4. 用 `insertZ` 数组保存第三组三个字符串。“自燃了……”。

放置事件处理器并补全：

1. 返回刚才的文本文件。
2. 将“3. 事件监听器和未完成的函数定义”标题项下的代码复制粘贴至 `main.js` 文件。这将：
    * 为 `randomize` 变量增加一个点击事件的监听器。于是当所引用的按钮被点击时，`result()` 函数就会运行。
    * 为代码添加一个未完成的 `result()` 函数定义。本测验剩下的工作就是完成这个函数，让程序正常运行起来。

补全 `result()` 函数：

1. 将 `newStory` 的值设置为 `storyText`。声明新变量有必要的，只有这样才能在每次按下按钮后、在函数运行时生成一个新的随机笑话。如果直接操作 `storyText` 则只能生成一次新故事
2. 将 `xItem`、`yItem` 和 `zItem` 分别设置为 `randomValueFromArray(insertX)`、`randomValueFromArray(insertY)` 和 `randomValueFromArray(insertZ)`。
3. 接下来将 `newStory` 中的占位符（`:inserta:`、`:insertb:` 和 `:insertc:` ）替换为 `xItem`、`yItem` 和 `zItem`。有专用的字符串方法可供使用，并用该方法的返回值为 `newStory` 赋值。每当按下按钮时，这些占位符都会替换为随机的笑话字符串。再给你一点提示，我们所说的这种方法每次只会将所找到的首个子字符串进行替换，因此该方法对某个占位符需要执行两次。
4. 在第一个 `if` 块中再次调用这个字符串替换方法，以使 `newStory` 字符串中的名字“李雷”替换为变量 `name` 的值。这里我们说：“如果 `customName` 中有值，就把故事里的“李雷”替换成它。”如果是汉化版将 newStory 中的“李雷”替换成 `name` 的值；
5. 在第二个 `if` 块中检查 `american` 单选按钮是否被选中。如果选中，就要将故事中的重量和温度值从公斤和摄氏度转换为磅和华氏度，具体事项如下：
    1. 确定英美单位的转换公式。
    2. 定义变量 `weight`、`temperature` 的行中，分别将美制单位按公式转化为英制，用 `Math.round()` 对计算结果取整。然后将英式单位连接到末尾。
    3. 就在上述两个变量的定义下方增加两个字符串置换操作，将“35 摄氏度”替换为 `temperature` 的值，将“140 公斤”替换为 `weight` 的值。
6. 最后，在函数倒数第二行，将 `story.textContent`（程序中显示笑话结果的段落）赋值为 `newStory`。
提示
--

* 除了在 HTML 文件中引入这个 JavaScript 文件之外，完全不需要编辑 HTML。
* 如果你不确定当前 JavaScript 是否正确添加到了你的 HTML 中，可以尝试暂时删除 JavaScript 文件的所有内容，然后加上一些简单但效果显著的 JavaScript 代码，保存文件并刷新浏览器。下面的示例会让 `<html>` 背景变为红色，如果 JavaScript 成功加载，那么整个浏览器窗口将变红：

```js
document.querySelector("html").style.backgroundColor = "red";
```

* `Math.round()` 是 Javascript 的内建函数，可取得与传入小数最接近的整数。
* 本示例中有三类字符串需要替换。可以多次重复 `replace()` 方法，也可使用正则表达式。例如：`var text = 'I am the biggest lover, I love my love';` 或 `text.replace(/love/g,'like');` 会将所有的“love”替换为“like”。记住，字符串本身是不可修改的！
测验
--

如果你是在课堂上进行这个测验，你可以把作品交给导师或教授去打分了。如果你是在自学，也可以在 本节测验的讨论页 或者 Mozilla 聊天室 的 #mdn 频道取得帮助。要自己先尝试，作弊是不会有收获的！

* 上一页
* Overview: First steps


技能测试：变量 - 学习 Web 开发

技能测试：变量
=======

本测试旨在评估读者对 存储所需信息 - 变量 一文的理解程度。

**备注：** 可在下文的互动编辑器中尝试完成评估，也可借助 CodePen、jsFiddle、Glitch 等在线编程实用工具完成任务。

可访问本页最后一节来 了解更多。

**备注：** 以下示例中，如果代码存在错误，将在互动编译器的输出框中显示提示信息来帮助你完成，（如果使用下载版本，则在浏览器的 JavaScript 控制台显示）

变量 1
----

本任务中，我们期望你做到：

* 声明 `myName` 变量。
* 在单独的一行中，用合适的值来初始化 `myName` 变量。（可用你自己的名字，或其他）
* 在一行内，声明 `myAge` 变量并初始化。

尝试更新下方互动编辑器中的代码来实现预期效果：
```

<head>
  <meta charset="utf-8" />
  <title>变量：任务1</title>
  <link
 rel="stylesheet"
 href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/tasks/styles.css" />
</head>

<body>
  <section class="preview"></section>
  <textarea class="playable playable-js" style="height: 280px;">
  // 在下方编写代码

  // 不要编写此行以下的代码！

  section.innerHTML = ' ';
  let para1 = document.createElement('p');
  para1.textContent = myName;
  let para2 = document.createElement('p');
  para2.textContent = myAge;
  section.appendChild(para1);
  section.appendChild(para2);
    </textarea
 >

  <div class="playable-buttons">
    <input id="reset" type="button" value="Reset" />
  </div>
</body>
<script class="editable"></script>
<script src="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/tasks/playable.js"></script>
```

下载 任务 1 的起始版本，然后使用你自选的编辑器完成任务。

变量 2
----

本任务中我们希望你将代码中 `myName` 当前保存的值修改为你自己的名字。

尝试更新下方互动编辑器中的代码来实现预期效果：
```

<head>
  <meta charset="utf-8" />
  <title>变量：任务2</title>
  <link
 rel="stylesheet"
 href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/tasks/styles.css" />
</head>

<body>
  <section class="preview"></section>

  <textarea class="playable playable-js" style="height: 220px;">

// 在下方编写代码

let myName = '韩梅梅';

// 不要编写此行以下的代码！

section.innerHTML = ' ';
let para = document.createElement('p');
para.textContent = myName;
section.appendChild(para);
    </textarea
 >

  <div class="playable-buttons">
    <input id="reset" type="button" value="Reset" />
  </div>
</body>
<script class="editable"></script>
<script src="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/tasks/playable.js"></script>
```

下载 任务 2 的起始版本，然后使用你自选的编辑器完成任务。

变量 3
----

现在是最后一个任务——现有代码中存在 2 处错误，请找出并修正，使输出窗口得到正确结果，即：“我是某某，20 年后我？岁。”

尝试更新下方互动编辑器中的代码来实现预期效果：
```

<head>
  <meta charset="utf-8" />
  <title>变量：任务3</title>
  <link
 rel="stylesheet"
 href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/tasks/styles.css" />
</head>

<body>
  <section class="preview"></section>

  <textarea class="playable playable-js" style="height: 300px;">
// 修改下面的代码

const myName = 'Default';
myName = '李雷';

let myAge = '18';

// 不要编写此行以下的代码！

section.innerHTML = ' ';
let para1 = document.createElement('p');
let para2 = document.createElement('p');
para1.textContent = `我是 ${myName}`;
para2.textContent = `20 年后我 ${myAge + 20} 岁`;
section.appendChild(para1);
section.appendChild(para2);
    </textarea
 >

  <div class="playable-buttons">
    <input id="reset" type="button" value="Reset" />
  </div>
</body>
<script class="editable"></script>
<script src="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/tasks/playable.js"></script>
```

下载 任务 3 的起始版本，然后使用你自选的编辑器完成任务。

了解更多
----

可以在互动编辑器中完成上面的练习。

如果你期望对练习结果进行评估，或者遇到困难需要帮助，可以：

1. 可借助 CodePen、jsFiddle、Glitch 等在线编程实用工具完成任务。在那里你可以自行编写代码，也可以使用上文中所给的初始代码。
2. 在 MDN 论坛学习区寻求他人的评估和帮助。你发表的文章应包括：
    * 一个描述性的标题，比如《请为“变量 1”水平测试进行评估》
    * 讲清你已经做出的努力，以及需要我们做什么，比如你遇到了困难并需要帮助，或者期望得到评估。
    * 一个链接指向你需要得到评估或帮助的实例，步骤 1 中三个在线编程工具之一均可。我们对希望得到帮助但由不提供代码的求助也很无奈。
    * 一个链接指向 MDN 测试的原始页面，在那里我们可以找到你需要获得帮助的原始信息。


有用的字符串方法 - 学习 Web 开发

有用的字符串方法
========

* 上一页
* Overview: First steps
* 下一页

现在我们学习基本的字符串语法，让我们开始思考一下我们可以对内置方法的字符串做什么有用的操作，例如查找文本字符串的长度，加入和分割字符串，将字符串中的一个字符替换为另一个字符。

|  |  |
| --- | --- |
| 前提： |
 基本的电脑知识，对 HTML 和 CSS 有一定的了解，最好是懂一点 javascript
 知识。
  |
| 目标： | 明白字符串这个对象，学会使用字符串的基本方法去处理字符串 |

把字符串当作对象
--------

我们曾经说过，现在我们重申一遍—在 javascript 中，一切东西都可以被当作对象。例如我们创建一个字符串。

```js
let string = "This is my string";
```

一旦你的变量成为字符串对象实例，你就可以有大量的原型和方法编辑它。如果你进入`String`对象页并观察页面旁边的列表你就会明白这一点。

**可能现在你的大脑开始迷糊了，不要担心！** 在你的学习进程中你真的不需要过早地理解大部分这方面知识，但是接下来我们这儿要看的是你要经常使用的一些知识。

现在我们在控制台中加些示例，我们已经提供了以下示例（你可在单独打开控制台标签或窗口，或者选择使用浏览器开发者控制台）
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>JavaScript console</title>
    <style>
 \* {
 box-sizing: border-box;
 }

 html {
 background-color: #0c323d;
 color: #809089;
 font-family: monospace;
 }

 body {
 max-width: 700px;
 }

 p {
 margin: 0;
 width: 1%;
 padding: 0 1%;
 font-size: 16px;
 line-height: 1.5;
 float: left;
 }

 .input p {
 margin-right: 1%;
 }

 .output p {
 width: 100%;
 }

 .input input {
 width: 96%;
 float: left;
 border: none;
 font-size: 16px;
 line-height: 1.5;
 font-family: monospace;
 padding: 0;
 background: #0c323d;
 color: #809089;
 }

 div {
 clear: both;
 }
 </style>
  </head>
  <body></body>

  <script>
 var geval = eval;
 function createInput() {
 var inputDiv = document.createElement("div");
 var inputPara = document.createElement("p");
 var inputForm = document.createElement("input");

 inputDiv.setAttribute("class", "input");
 inputPara.textContent = ">";
 inputDiv.appendChild(inputPara);
 inputDiv.appendChild(inputForm);
 document.body.appendChild(inputDiv);

 inputForm.addEventListener("change", executeCode);
 }

 function executeCode(e) {
 try {
 var result = geval(e.target.value);
 } catch (e) {
 var result = "error — " + e.message;
 }

 var outputDiv = document.createElement("div");
 var outputPara = document.createElement("p");

 outputDiv.setAttribute("class", "output");
 outputPara.textContent = "Result: " + result;
 outputDiv.appendChild(outputPara);
 document.body.appendChild(outputDiv);

 e.target.disabled = true;
 e.target.parentNode.style.opacity = "0.5";

 createInput();
 }

 createInput();
 </script>
</html>
```

### 获得字符串的长度

这很简单 — 你可以很轻松的使用 `length` 属性。尝试输入以下的两行代码：

```js
let browserType = "mozilla";
browserType.length;
```

这个结果应该返回一个数字：7，因为"mozilla"的长度为 7 个字符。说字符串的长度有用是有很多原因的，例如，你可能想算出一连串名字的长度，并用名字长度来作为名字排序的依据，亦或让一个用户知道他输入的用户名太长，已经超出了输入的字符串长度限制。

### 检索特定字符串字符

在相关的注释中，你可以使用方括号表示法返回字符串中的任何字符 - 这意味着你可以在变量名的末尾包含方括号（[ ]）。在方括号内，你可以包含要返回的字符的编号，例如，你要检索第一个字母，可以这样做：

```js
browserType[0];
```

电脑从 0 开始，不是 1！要检索任何字符串的最后一个字符，我们可以使用下面这行，将这种技术与我们上面看到的 length 属性相结合起来：

```js
browserType[browserType.length - 1];
```

“mozilla”的长度为 7，但由于计数从 0 开始，所以字符位置为 6，因此需要长度为**length-1**。例如，你可以使用它来查找一系列字符串的第一个字母，并按字母顺序排列。

### 在字符串中查找子字符串并提取它

1. 有时候你会想要找出一个较小的字符串是否存在于一个较大的字符串中（我们通常会说一个字符串中存在一个子字符串）。这可以使用`indexOf()`方法来完成，该方法需要一个parameter (en-US) — 你想要搜索的子字符串。尝试以下：

```js
browserType.indexOf("zilla");
```

结果是 2，因为子字符串“zilla”从“mozilla”内的位置 2（0、1、2——所以从第 3 个字符）开始。这样的代码可以用来过滤字符串。例如，假设我们有一个 Web 地址列表，但我们只想打印出包含“mozilla”的那些地址。
2. 这可以用另一种可能更有效的方式来实现。尝试以下：

```js
browserType.indexOf("vanilla");
```

这应该会得到 `-1` 的结果——当在主字符串中找不到子字符串（在本例中为“vanilla”）时将返回 `-1`。
 你可以使用它来查找不包含子串“mozilla”的所有字符串实例，或者如果使用否定运算符，请执行以下操作。你可以这样做：

```js
if (browserType.indexOf("mozilla") !== -1) {
  // do stuff with the string
}
```

3. 当你知道字符串中的子字符串开始的位置，以及想要结束的字符时，`slice()`可以用来提取 它。尝试以下：

```js
browserType.slice(0, 3);
```

这时返回"moz"——第一个参数是开始提取的字符位置，第二个参数是提取的最后一个字符的后一个位置。所以提取从第一个位置开始，直到但不包括最后一个位置。（此例中）你也可以说第二个参数等于被返回的字符串的长度。
4. 此外，如果你知道要在某个字符之后提取字符串中的所有剩余字符，则不必包含第二个参数，而只需要包含要从中提取的字符位置 字符串中的其余字符。尝试以下：

```js
browserType.slice(2);
```

这返回“zilla”——这是因为 2 的字符位置是字母 z，并且因为没有包含第二个参数，所以返回的子字符串是字符串中的所有剩余字符。

**备注：** `slice()`的第二个参数是可选的：如果没有传入这个参数，分片结束位置会在原始字符串的末尾。这个方法也有其他的选项；学习`slice()`这页，来看看你还能发现什么其他作用。

### 转换大小写

字符串方法`toLowerCase()`和`toUpperCase()`字符串并将所有字符分别转换为小写或大写。例如，如果要在将数据存储在数据库中之前对所有用户输入的数据进行规范化，这可能非常有用。

让我们尝试输入以下几行来看看会发生什么：

```js
let radData = "My NaMe Is MuD";
radData.toLowerCase();
radData.toUpperCase();
```

### 替换字符串的某部分

你可以使用`replace()`方法将字符串中的一个子字符串替换为另一个子字符串。在基础的层面上，这个工作非常简单。你当然可以用它做一些更高级的事情，但目前我们不会涉及到。

它需要两个参数 - 要被替换下的字符串和要被替换上的字符串。尝试这个例子：

```js
browserType.replace("moz", "van");
```

注意，在实际程序中，想要真正更新 `browserType` 变量的值，你需要设置变量的值等于刚才的操作结果；它不会自动更新子串的值。所以事实上你需要这样写：`browserType = browserType.replace('moz','van');`。

主动学习的示例
-------

在本节中，我们会让你尽力编写一些字符串操作代码。在下面的每个练习中，我们有一个字符串数组，一个循环，用于处理数组中的每个值，并将其显示在项目符号列表中。你现在不需要了解数组或循环 - 这些将在以后的文章中解释。所有你需要做的每一种情况都是写出将以我们想要的格式输出字符串的代码。

每个示例都附带一个“重置”按钮，你可以使用该按钮重置代码，如果你犯了错误，并且无法再次工作，并且显示解决方案按钮，你可以按下来看到一个潜在的答案，如果你真的卡住了。

### 过滤问候留言

在第一个练习中，我们将简单介绍一下 - 我们有一系列的问候卡片消息，但我们希望对它们进行排序，以列出圣诞消息。我们希望你在 `if(...)` 结构中填写条件测试，以测试每个字符串，并将其打印在列表中，如果它是圣诞消息。

1. 首先考虑一下如何测试每种情况下的消息是否为圣诞消息。所有这些消息中都有什么字符串，你可以使用什么方法来测试是否存在？
2. 然后，你需要编写 *操作数 1 操作符 操作数 2* 形式的条件测试。左边的东西等于右边的东西吗？或者在这种情况下，方法调用在左边返回的结果在右边？
3. 提示：在这种情况下，测试方法调用是否不等于某个结果可能更有用。
```

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<textarea id="code" class="playable-code" style="height: 290px;">
var list = document.querySelector('.output ul');
list.innerHTML = '';
var greetings = ['Happy Birthday!',
                 'Merry Christmas my love',
                 'A happy Christmas to all the family',
                 'You\'re all I want for Christmas',
                 'Get well soon'];

for (var i = 0; i < greetings.length; i++) {
  var input = greetings[i];
  // Your conditional test needs to go inside the parentheses
  // in the line below, replacing what's currently there
  if (greetings[i]) {
    var result = input;
    var listItem = document.createElement('li');
    listItem.textContent = result;
    list.appendChild(listItem);
  }
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  updateCode();
});

solution.addEventListener("click", function () {
  textarea.value = jsSolution;
  updateCode();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nlist.innerHTML = '';\nvar greetings = ['Happy Birthday!',\n 'Merry Christmas my love',\n 'A happy Christmas to all the family',\n 'You\\'re all I want for Christmas',\n 'Get well soon'];\n\nfor(var i = 0; i < greetings.length; i++) {\n var input = greetings[i];\n if(greetings[i].indexOf('Christmas') !== -1) {\n var result = input;\n var listItem = document.createElement('li');\n listItem.textContent = result;\n list.appendChild(listItem);\n }\n}";

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
```

### 大写修正

在这个练习中，我们有英国城市的名字，但是这个大写字母都搞砸了。我们希望你改变它们，使它们都是小写字母，除了首字母。一个很好的方法是：

1. 将输入变量中包含的整个字符串转换为小写，并将其存储在新变量中。
2. 在此新变量中获取字符串的第一个字母并将其存储在另一个变量中。
3. 将此最新变量用作子字符串，将小写字符串的第一个字母从小写更改为大写。将此替换过程的结果存储在另一个新变量中。
4. 让 `result` 变量的值与最终结果相等，而不是使用 `input` 变量。

**备注：** 一个提示 - 字符串方法的参数不必是字符串文字; 它们也可以是变量，甚至是在其上调用方法的变量。
```

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<textarea id="code" class="playable-code" style="height: 250px;">
var list = document.querySelector('.output ul');
list.innerHTML = '';
var cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL'];
for(var i = 0; i < cities.length; i++) {
  var input = cities[i];
  // write your code just below here

  var result = input;
  var listItem = document.createElement('li');
  listItem.textContent = result;
  list.appendChild(listItem);
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  updateCode();
});

solution.addEventListener("click", function () {
  textarea.value = jsSolution;
  updateCode();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nlist.innerHTML = '';\nvar cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL'];\n\nfor(var i = 0; i < cities.length; i++) {\n var input = cities[i];\n var lower = input.toLowerCase();\n var firstLetter = lower.slice(0,1);\n var capitalized = lower.replace(firstLetter,firstLetter.toUpperCase());\n var result = capitalized;\n var listItem = document.createElement('li');\n listItem.textContent = result;\n list.appendChild(listItem);\n\n}";

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
```

### 从原先部分得到新字符串

在最后一个练习中，阵列包含一堆字符串，其中包含有关英格兰北部火车站的信息。字符串是包含三字母站代码的数据项，后面是一些机器可读数据，后跟分号，后跟可读站名。例如：
```

MAN675847583748sjt567654;Manchester Piccadilly
```

我们要提取站点代码和名称，并将它们放在一起，具有以下结构的字符串：
```

MAN: Manchester Piccadilly
```

我们建议这样做：

1. 提取三个字母的站代码并将其存储在一个新的变量中。
2. 查找分号的字符索引号。
3. 使用分号字符索引号作为参考点提取人可读的站名，并将其存储在新变量中。
4. 连接两个新的变量和一个字符串文字，使最终的字符串。
5. 将 `result` 变量的值更改为等于最终的字符串，而不是 `input`。
```

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<textarea id="code" class="playable-code" style="height: 285px;">
var list = document.querySelector('.output ul');
list.innerHTML = '';
var stations = ['MAN675847583748sjt567654;Manchester Piccadilly',
                'GNF576746573fhdg4737dh4;Greenfield',
                'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street',
                'SYB4f65hf75f736463;Stalybridge',
                'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield'];

for (var i = 0; i < stations.length; i++) {
  var input = stations[i];
  // write your code just below here

  var result = input;
  var listItem = document.createElement('li');
  listItem.textContent = result;
  list.appendChild(listItem);
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  updateCode();
});

solution.addEventListener("click", function () {
  textarea.value = jsSolution;
  updateCode();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nlist.innerHTML = '';\nvar stations = ['MAN675847583748sjt567654;Manchester Piccadilly',\n 'GNF576746573fhdg4737dh4;Greenfield',\n 'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street',\n 'SYB4f65hf75f736463;Stalybridge',\n 'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield'];\n\nfor(var i = 0; i < stations.length; i++) {\n var input = stations[i];\n var code = input.slice(0,3);\n var semiC = input.indexOf(';');\n var name = input.slice(semiC + 1);\n var final = code + ': ' + name;\n var result = final;\n var listItem = document.createElement('li');\n listItem.textContent = result;\n list.appendChild(listItem);\n}";

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
```

结论
--

你不能逃避的事实是在编程中处理单词和句子是非常重要的——特别是在 JavaScript 中，因为网站都是关于与人沟通的。本文已经给出了你现在需要了解的关于操作字符串的基础知识。这将在未来进入更复杂的主题时为你服务。接下来，我们将在短期内研究我们需要关注的最后一个主要的数据类型——数组。

* 上一页
* Overview: First steps
* 下一页


数组 - 学习 Web 开发

数组
==

* 上一页
* Overview: First steps
* 下一页

在本模块的最后一篇文章中，我们将看看数组——一种将一组数据存储在单个变量名下的优雅方式。现在我们看看它有什么用，然后探索如何来创建一个数组，检索、添加和删除存储在数组中的元素，以及其他更多的功能。

|  |  |
| --- | --- |
| 前提： | 基本的电脑知识，对 HTML、CSS 语法有基础的理解，能理解什么是 JavaScript。 |
| 目标： | 理解什么是数组，和如何在 JavaScript 中操作数组。 |

数组是什么？
------

数组通常被描述为“像列表一样的对象”; 简单来说，数组是一个包含了多个值的对象。数组对象可以存储在变量中，并且能用和其他任何类型的值完全相同的方式处理，区别在于我们可以单独访问列表中的每个值，并使用列表执行一些有用和高效的操作，如循环 - 它对数组中的每个元素都执行相同的操作。也许我们有一系列产品和价格存储在一个数组中，我们想循环遍历所有这些产品，并将它们打印在发票上，同时将所有产品的价格统计在一起，然后将总价格打印在底部。

如果我们没有数组，我们必须将每个产品存储在一个单独的变量中，然后调用打印的代码，并为每个产品单独添加。花费的时间要长得多，效率很低，而且也容易出错。如果我们有 10 个产品需要添加发票，那就只是有点麻烦而已，但是 100 个，或者 1000 个呢？我们稍后将在文章中使用这个例子。

像以前的文章一样，我们通过在 JavaScript 控制台中输入一些示例来了解数组的基础知识。我们在下面提供了一个（你也可以在单独的选项卡或窗口中打开此控制台，或者如果你愿意，请使用浏览器的开发者工具控制台）。
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>JavaScript console</title>
    <style>
 \* {
 box-sizing: border-box;
 }

 html {
 background-color: #0c323d;
 color: #809089;
 font-family: monospace;
 }

 body {
 max-width: 700px;
 }

 p {
 margin: 0;
 width: 1%;
 padding: 0 1%;
 font-size: 16px;
 line-height: 1.5;
 float: left;
 }

 .input p {
 margin-right: 1%;
 }

 .output p {
 width: 100%;
 }

 .input input {
 width: 96%;
 float: left;
 border: none;
 font-size: 16px;
 line-height: 1.5;
 font-family: monospace;
 padding: 0;
 background: #0c323d;
 color: #809089;
 }

 div {
 clear: both;
 }
 </style>
  </head>
  <body></body>

  <script>
 var geval = eval;
 function createInput() {
 var inputDiv = document.createElement("div");
 var inputPara = document.createElement("p");
 var inputForm = document.createElement("input");

 inputDiv.setAttribute("class", "input");
 inputPara.textContent = ">";
 inputDiv.appendChild(inputPara);
 inputDiv.appendChild(inputForm);
 document.body.appendChild(inputDiv);

 if (document.querySelectorAll("div").length > 1) {
 inputForm.focus();
 }

 inputForm.addEventListener("change", executeCode);
 }

 function executeCode(e) {
 try {
 var result = geval(e.target.value);
 } catch (e) {
 var result = "error — " + e.message;
 }

 var outputDiv = document.createElement("div");
 var outputPara = document.createElement("p");

 outputDiv.setAttribute("class", "output");
 outputPara.textContent = "Result: " + result;
 outputDiv.appendChild(outputPara);
 document.body.appendChild(outputDiv);

 e.target.disabled = true;
 e.target.parentNode.style.opacity = "0.5";

 createInput();
 }

 createInput();
 </script>
</html>
```

### 创建数组

数组由方括号构成，其中包含用逗号分隔的元素列表。

1. 假设我们想在一个数组中存储一个购物清单 - 我们会做一些像下面这样的事情。在你的控制台中输入以下行：

```js
let shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
shopping;
```

2. 在这种情况下，数组中的每个项目都是一个字符串，但请记住，你可以将任何类型的元素存储在数组中 - 字符串，数字，对象，另一个变量，甚至另一个数组。你也可以混合和匹配项目类型 - 它们并不都是数字，字符串等。尝试下面这些：

```js
let sequence = [1, 1, 2, 3, 5, 8, 13];
let random = ["tree", 795, [0, 1, 2]];
```

3. 尝试创建你自己的几个数组，然后再继续往下看。
### 访问和修改数组元素

然后，你可以使用括号表示法访问数组中的元素，与 检索特定字符串字符 的方法相同。

1. 在你的控制台中输入以下内容：

```js
shopping[0];
// returns "bread"
```

2. 你还可以简单地为单个数组元素提供新值来修改数组中的元素。例如：

```js
shopping[0] = "tahini";
shopping;
// shopping will now return [ "tahini", "milk", "cheese", "hummus", "noodles" ]
```

**备注：** 我们以前说过，但还是提醒一下——电脑从 0 开始计数！
3. 请注意，数组中包含数组的话称之为多维数组。你可以通过将两组方括号链接在一起来访问数组内的另一个数组。例如，要访问数组内部的一个项目，即 `random` 数组中的第三个项目（参见上一节），我们可以这样做：

```js
random[2][2];
```

4. 在继续之前，尝试对你的数组示例进行一些修改。玩一玩，看看哪些有效，哪些无效。
### 获取数组长度

你可以通过使用 `length` 属性获取数组的长度（数组中有多少项元素），这与查找字符串的长度（以字符为单位）完全相同。尝试以下代码：

```js
sequence.length;
// should return 7
```

虽然 length 属性也有其他用途，但最常用于循环（循环遍历数组中的所有项）。例如：

```js
let sequence = [1, 1, 2, 3, 5, 8, 13];
for (let i = 0; i < sequence.length; i++) {
  console.log(sequence[i]);
}
```

你将在以后的文章中正确地了解循环，但简而言之，这段代码的意思是：

1. 在数组中的元素编号 0 开始循环。
2. 在元素编号等于数组长度的时候停止循环。这适用于任何长度的数组，但在这种情况下，它将在编号 7 的时候终止循环（这很好，因为我们希望最后一位元素的编号是 6）。
3. 对于每个元素，使用 console.log() 将其打印到浏览器控制台。
一些有用的数组方法
---------

在本节中，我们将介绍一些相当有用的数组方法，这些方法允许我们将字符串拆分为字符串数组，反之亦然，以及添加或删除元素。

### 字符串和数组之间的转换

通常，你会看到一个包含在一个长长的字符串中的原始数据，你可能希望将有用的项目分成更有用的表单，然后对它们进行处理，例如将它们显示在数据表中。为此，我们可以使用 `split()` 方法。在其最简单的形式中，这需要一个参数，你要将字符串分隔的字符，并返回分隔符之间的子串，作为数组中的项。

**备注：** 好吧，从技术上讲，这是一个字符串方法，而不是一个数组方法，但是我们把它放在数组中，因为它在这里很合适。

1. 我们来玩一下这个方法，看看它是如何工作的。首先，在控制台中创建一个字符串：

```js
let myData = "Manchester,London,Liverpool,Birmingham,Leeds,Carlisle";
```

2. 现在我们用每个逗号分隔它：

```js
let myArray = myData.split(",");
myArray;
```

3. 最后，尝试找到新数组的长度，并从中检索一些项目：

```js
myArray.length;
myArray[0]; // the first item in the array
myArray[1]; // the second item in the array
myArray[myArray.length - 1]; // the last item in the array
```

4. 你也可以使用 `join()` 方法进行相反的操作。尝试以下：

```js
let myNewString = myArray.join(",");
myNewString;
```

5. 将数组转换为字符串的另一种方法是使用 `toString()` 方法。 `toString()` 可以比 `join()` 更简单，因为它不需要一个参数，但更有限制。使用 `join()` 可以指定不同的分隔符（尝试使用与逗号不同的字符运行步骤 4）。

```js
let dogNames = ["Rocket", "Flash", "Bella", "Slugger"];
dogNames.toString(); //Rocket,Flash,Bella,Slugger
```

### 添加和删除数组项

我们还没有涵盖添加和删除数组元素，现在让我们来看看。我们将使用在上一节中最后提到的 `myArray` 数组。如果你尚未遵循该部分，请先在控制台中创建数组：

```js
let myArray = [
  "Manchester",
  "London",
  "Liverpool",
  "Birmingham",
  "Leeds",
  "Carlisle",
];
```

首先，要在数组末尾添加或删除一个项目，我们可以使用 `push()` 和 `pop()`。

1. 让我们先使用 `push()`——注意，你需要添加一个或多个要添加到数组末尾的元素。尝试下面的代码：

```js
myArray.push("Cardiff");
myArray;
myArray.push("Bradford", "Brighton");
myArray;
```

2. 当方法调用完成时，将返回数组的新长度。如果要将新数组长度存储在变量中。例如：

```js
var newLength = myArray.push("Bristol");
myArray;
newLength;
```

3. 从数组中删除最后一个元素的话直接使用 `pop()` 就可以。例如：

```js
myArray.pop();
```

4. 当方法调用完成时，将返回已删除的项目。你也可以这样做：

```js
let removedItem = myArray.pop();
myArray;
removedItem;
```

`unshift()` 和 `shift()` 从功能上与 `push()` 和 `pop()` 完全相同，只是它们分别作用于数组的开始，而不是结尾。

1. 首先 `unshift()`——尝试一下这个命令：

```js
myArray.unshift("Edinburgh");
myArray;
```

2. 现在 `shift()`——尝试一下！

```js
let removedItem = myArray.shift();
myArray;
removedItem;
```

积极学习：打印这些产品
-----------

我们回到前面描述的例子——打印出发票上的产品名称和价格，然后计算总价格并将其印在底部。在下面的可编辑示例中，包含数字的注释——每个注释标记都是你必须向代码添加内容的地方。它们如下：

1. 在 `// number 1` 注释下面是一些字符串，每个字符串包含一个产品名称和一个冒号分隔的价格。我们希望你将其转换为一个数组，并将其存储在名为 `products` 的数组中。
2. 与 `// number 2` 注释同一行的是 for 循环的开头。在这行中，我们目前有 `i <= 0`，这是一个条件测试，导致 for 循环 立即停止，因为它说“当 `i` 不再小于或等于 0”时停止，而 `i` 从 0 开始。我们希望你使用条件测试来替换它，当 `i` 不再小于 `products` 数组的长度时，该条件测试会停止循环。
3. 就在 `// number 3` 注释的下方，我们希望你编写一行代码，将当前数组项目（名称：价格）分成两个独立的项目，一个只包含该名称，一个只包含该价格。如果你不确定如何执行此操作，请参阅有用的字符串方法文章以获得一些帮助，甚至更好的看看本文中的字符串和数组之间的转换部分。
4. 作为上述代码行的一部分，你还需要将价格从字符串转换为数字。如果你不记得如何做，请查看第一个字符串文章。
5. 有一个名为 `total` 的变量被创建，并在代码的顶部赋值为 0。在循环内（在 `// number 4` 注释下面），我们希望你添加一行，将当前项目价格添加到循环中的迭代变量，以便在代码结尾处将正确的总数打印到发票上。你可能需要一个赋值运算符来执行此操作。
6. 我们希望你改变 `// number 5` 注释的行，以便使 `itemText` 变量等于“当前项目名称 - $ 当前项目价格”，例如“Shoes - $ 23.99”，以此将每个项目的正确信息都印在发票上。这只是简单的字符串连接，你应该对此很熟悉。
```

<div class="output" style="min-height: 150px;">
  <ul></ul>

  <p></p>
</div>

<textarea id="code" class="playable-code" style="height: 370px;">
var list = document.querySelector('.output ul');
var totalBox = document.querySelector('.output p');
var total = 0;
list.innerHTML = '';
totalBox.textContent = '';
// number 1
                'Underpants:6.99'
                'Socks:5.99'
                'T-shirt:14.99'
                'Trousers:31.99'
                'Shoes:23.99';

for (var i = 0; i <= 0; i++) { // number 2
  // number 3

  // number 4

  // number 5
  itemText = 0;

  var listItem = document.createElement('li');
  listItem.textContent = itemText;
  list.appendChild(listItem);
}

totalBox.textContent = 'Total: $' + total.toFixed(2);
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  updateCode();
});

solution.addEventListener("click", function () {
  textarea.value = jsSolution;
  updateCode();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nvar totalBox = document.querySelector('.output p');\nvar total = 0;\nlist.innerHTML = '';\ntotalBox.textContent = '';\n\nvar products = ['Underpants:6.99',\n 'Socks:5.99',\n 'T-shirt:14.99',\n 'Trousers:31.99',\n 'Shoes:23.99'];\n\nfor(var i = 0; i < products.length; i++) {\n var subArray = products[i].split(':');\n var name = subArray[0];\n var price = Number(subArray[1]);\n total += price;\n itemText = name + ' — $' + price;\n\n var listItem = document.createElement('li');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n}\n\ntotalBox.textContent = 'Total: $' + total.toFixed(2);";

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
```

积极学习：前 5 个搜索
------------

当你在 Web 程序中维护当前活动元素的记录时，`push()` 和 `pop()` 是不错的方法。例如，在动画场景中，你可能会有一系列表示当前显示的背景图像的对象，并且由于性能或混乱原因，你可能只需要一次显示 50 个。当创建新对象并将其添加到数组中时，可以从数组中删除较旧的对象以保持所需的数量。

在这个例子中，我们将展示一种更简单的使用方法 - 在这里我们给你一个假的搜索网站，一个搜索框。这个想法是，当在搜索框中输入时，列表中会显示 5 个先前的搜索字词。当列表项目数量超过 5 时，每当新项目被添加到顶部时，最后一个项目开始被删除，因此总是显示 5 个以前的搜索字词。

**备注：** 在真正的搜索应用中，你可能可以点击先前的搜索字词返回上一次搜索，并显示实际的搜索结果！我们现在只是保持简单的逻辑。

要完成该应用程序，我们需要你：

1. 在 `//number 1` 注释下面添加一行，将输入到搜索框中的当前值添加到数组的开头。这可以使用 `searchInput.value` 检索。
2. 在 `// number 2` 注释下方添加一行，该行删除数组末尾的当前值。
```

<div class="output" style="min-height: 150px;">
  <input type="text" /><button>Search</button>

  <ul></ul>
</div>

<textarea id="code" class="playable-code" style="height: 370px;">
var list = document.querySelector('.output ul');
var searchInput = document.querySelector('.output input');
var searchBtn = document.querySelector('.output button');

list.innerHTML = '';

var myHistory = [];

searchBtn.onclick = function() {
  // 如果搜索框不为空，我们则将搜索词添加到开头
  if (searchInput.value !== '') {
    // number 1

    // 清空显示的搜索关键词列表，防止显示
    // 每次输入搜索词都会重新生成显示的内容
    list.innerHTML = '';

    // 通过循环遍历，显示所有的搜索关键词
    for (var i = 0; i < myHistory.length; i++) {
      var itemText = myHistory[i];
      var listItem = document.createElement('li');
      listItem.textContent = itemText;
      list.appendChild(listItem);
    }

    // 如果数组的长度大于 5，那么便移除旧的搜索关键词
    if (myHistory.length >= 5) {
      // number 2

    }

    // 清空并聚焦到搜索框，准备下一次的搜索
    searchInput.value = '';
    searchInput.focus();
  }
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  updateCode();
});

solution.addEventListener("click", function () {
  textarea.value = jsSolution;
  updateCode();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nvar searchInput = document.querySelector('.output input');\nvar searchBtn = document.querySelector('.output button');\n\nlist.innerHTML = '';\n\nvar myHistory= [];\n\nsearchBtn.onclick = function() {\n if(searchInput.value !== '') {\n myHistory.unshift(searchInput.value);\n\n list.innerHTML = '';\n\n for(var i = 0; i < myHistory.length; i++) {\n var itemText = myHistory[i];\n var listItem = document.createElement('li');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n }\n\n if(myHistory.length >= 5) {\n myHistory.pop();\n }\n\n searchInput.value = '';\n searchInput.focus();\n }\n}";

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
```

总结
--

阅读本文后，我们相信你会明白数组是很有用的; 你会看到它们在 JavaScript 中随处可见，通常与循环相关，以便对数组中的每个元素做同样的事情。我们将教你所有有用的基础知识，了解下一个模块中的循环，但现在你应该给自己鼓掌，并稍加休息; 你已经完成了本单元中的所有文章！

唯一需要做的就是通过这个模块的评估，这将测试你对之前的文章的理解。

参见
--

* Indexed collections — 数组及其表兄弟类型阵列的高级指导。
* `Array` — Array 对象引用页面 - 有关此页面中讨论功能的详细参考指南等。
* 上一页
* Overview: First steps
* 下一页


文本处理——JavaScript 中的字符串 - 学习 Web 开发

文本处理——JavaScript 中的字符串
======================

* 上一页
* Overview: First steps
* 下一页

接下来，我们将把注意力转向字符串——也就是编程中所说的文本片段。在本文中，我们将了解在学习 JavaScript 时，你应该了解的关于字符串的所有常见内容，例如创建字符串、在字符串中转义引号和连接字符串。

|  |  |
| --- | --- |
| 前提： | 基本的计算机读写能力，对 HTML 和 CSS 的基本理解，对 JavaScript 的理解。 |
| 目标： | 熟悉 JavaScript 中字符串的基本知识。 |

语言的力量
-----

语言对人类非常重要——它们是我们交流的重要组成部分。由于 Web 是一种主要基于文本的媒介，旨在让人们进行交流和分享信息，因此对我们来说，掌握它所出现的文本是很有用的。HTML 为我们的文本提供了结构和意义，CSS 允许我们精确地设计它的样式，JavaScript 包含许多操作字符串的特性：创建定制的欢迎消息、在需要时显示正确的文本标签、将术语排序成所需的顺序，等等。

到目前为止，我们在课程中展示的所有程序都涉及到一些字符串操作。

创建字符串
-----

乍一看，字符串的操作方式与数字类似，但当你深入研究时，你会发现一些显著的差异。让我们在浏览器开发者控制台中输入一些基本的代码来熟悉一下。

首先，输入以下几行：

```js
const string = "这场革命将不会被电视转播。";
console.log(string);
```

就像我们处理数字一样，我们声明一个变量，并用一个字符串值初始化它，然后返回值。这里唯一的区别是，在编写字符串时，我们需要在字符串值的两边加上引号。

如果你不这样做，或者在书写过程中，漏掉其中一个引号，你就会得到一个错误。试着输入以下几行：

```js
const badString1 = 这是一个测试;
const badString2 = '这是一个测试;
const badString3 = 这是一个测试';
```

这几行不起作用，因为没有用引号包裹的任何文本字符串都被假定为变量名、属性名、保留字，等等。如果浏览器不能找到它，那么将会引发错误（例如：“missing; before statement”）。如果浏览器能够识别字符串从哪里开始，但是不能找到字符串的结尾符，如第二行所示，那么它也会引发错误（带有“unterminated string literal”)。如果你写的程序目前也引发这样的错误，那么请回过头来仔细检查所有字符串，以确保没有漏写引号。

如果你之前定义了变量 `string`，下面的操作将会起作用——尝试一下：

```js
const badString = string;
console.log(badString);
```

`badString` 的值现在被设置为了与 `string` 具有相同的值。

### 单引号、双引号和反引号

在 JavaScript 中，你可以选择单引号（`'`）、双引号（`"`）或反引号（```）来包裹字符串。以下所有方法都可以：

```js
const single = "单引号";
const double = "双引号";
const backtick = `反引号`;

console.log(single);
console.log(double);
console.log(backtick);
```

字符串的开头和结尾必须使用相同的字符，否则会出现错误：

```js
const badQuotes = '不允许这样写！";
```

使用单引号声明的字符串和使用双引号声明的字符串是相同的，你可以根据个人偏好来使用。但推荐选择一种样式并在代码中保持一致。

使用反引号声明的字符串是一种特殊字符串，被称为*模板字面量*。在大多数情况下，模板字面量与普通字符串类似，但它具有一些特殊的属性：

* 你可以在其中嵌入 JavaScript
* 你可以声明多行的模板字面量
嵌入 JavaScript
-------------

在模板字面量中，你可以在 `${ }` 中包装 JavaScript 变量或表达式，其结果将被包含在字符串中：

```js
const name = "克里斯";
const greeting = `你好，${name}`;
console.log(greeting); // "你好，克里斯"
```

你可以使用相同的技术来连接两个变量：

```js
const one = "你好，";
const two = "请问最近如何？";
const joined = `${one}${two}`;
console.log(joined); // "你好，请问最近如何？"
```

像这样连接字符串被称为*串联*（concatenation）。

### 上下文中的串联

让我们看一下实际使用的串联：

```html
<button>按这里</button>

```

```js
const button = document.querySelector("button");

function greet() {
  const name = prompt("你叫什么名字？");
  const greeting = document.querySelector("#greeting");
  greeting.textContent = `你好呀，${name}！很高兴见到你！`;
}

button.addEventListener("click", greet);
```

这里我们使用了第 4 行中的 `window.prompt()` 函数，它要求用户在弹出的对话框中回答一个问题然后将他们输入的文本存储在一个给定的变量中——在这个示例中是 `name` 变量。然后，我们将名称插入通用的问候消息中，并显示该字符串。

### 使用“+”连接字符串

你只能将 `${}` 与模板字面量一起使用，而不能与不同字符串一起使用。你可以使用 `+` 运算符来连接普通字符串。

```js
const greeting = "你好";
const name = "克里斯";
console.log(greeting + "，" + name); // "你好，克里斯"
```

但是，模板字面量通常更具可读性：

```js
const greeting = "你好";
const name = "克里斯";
console.log(`${greeting}，${name}`); // "你好，克里斯"
```

### 在字符串中包含表达式

除了变量，你还可以在模板字面量中包含 JavaScript 表达式，表达式的结果将包含在最终的结果中：

```js
const song = "青花瓷";
const score = 9;
const highestScore = 10;
const output = `我喜欢歌曲《${song}》。我给它打了 ${
 (score / highestScore) \* 100
} 分。`;
console.log(output); // "我喜欢歌曲《青花瓷》。我给它打了 90 分。"
```

多行字符串
-----

模板字符串会保留源代码中的换行符，因此你可以编写跨越多行的字符串，如下所示：

```js
const newline = `终于有一天，
你知道了必须做的事情，而且开始……`;
console.log(newline);

/\*
终于有一天，
你知道了必须做的事情，而且开始……
\*/
```

要使用普通字符串获得等效的输出，你必须在字符串中包含换行字符（`\n`）：

```js
const newline = "终于有一天，\n你知道了必须做的事情，而且开始……";
console.log(newline);

/\*
终于有一天，
你知道了必须做的事情，而且开始……
\*/
```

有关更多示例和高级特性的详细信息，请参阅我们的模板字面量参考页面。

在字符串中包含引号
---------

既然我们使用引号来表示字符串的开始和结束，那么我们如何在字符串中包含实际的引号呢？我们知道这样做是不行的：

```js
const badQuotes = "She said "I think so!"";
```

一种常见的方法是换用其他字符来声明字符串：

```js
const goodQuotes1 = 'She said "I think so!"';
const goodQuotes2 = `She said "I'm not going in there!"`;
```

另一种选择是*转义*存在问题的引号。转义字符意味着我们对它们做了一些处理，以确保它们被识别为文本，而不是代码的一部分。在 JavaScript 中，我们通过在字符之前加上反斜杠来实现这一点。试试这个：

```js
const bigmouth = 'I\'ve got no right to take my place…';
console.log(bigmouth);
```

你可以使用相同的技术来插入其他特殊字符。有关更多详细信息，请参阅转义序列。

数字与字符串
------

当我们尝试连接一个字符串和数字时会发生什么？让我们在控制台中尝试一下：

```js
const name = "Front ";
const number = 242;
console.log(name + number); // "Front 242"
```

你可能会认为这会返回一个错误，但它可以正常工作。数字应该如何显示为字符串是相当明确的，所以浏览器会自动将数字转换为字符串，并将两个字符串连接起来。

如果你有一个数字变量，你想将其转换为字符串，或者你想将一个字符串变量转换为数字，你可以使用以下两个结构：

* 如果可以的话，`Number()` 函数会将其参数转换为数字。试试这个：

```js
const myString = "123";
const myNum = Number(myString);
console.log(typeof myNum);
// number
```

* 相反，`String()` 函数将其参数转换为字符串。试试这个：

```js
const myNum2 = 123;
const myString2 = String(myNum2);
console.log(typeof myString2);
// string
```

这些结构在某些情况下非常有用。例如，如果用户在表单的文本字段中输入一个数字，那么它就是一个字符串。然而，如果你想将这个数字与某个值相加，则需要它是一个数字，因此你可以通过 `Number()` 来处理它。我们在数字猜谜游戏的第 59 行是这样做的。

结论
--

这就是 JavaScript 中所涉及的字符串的基础知识。在下一篇文章中，我们将在此基础上，研究 JavaScript 中字符串的一些内置方法，以及如何使用它们来操作我们的字符串，使之成为我们想要的形式。

* 上一页
* Overview: First steps
* 下一页


如何存储你需要的信息——变量 - 学习 Web 开发

如何存储你需要的信息——变量
==============

* 上一页
* Overview: First steps
* 下一页

在读完之前的一些文章之后，你现在应该大概知道了 JavaScript 是什么，你能用它干什么，它是怎么跟其他 web 技术协同工作的，以及从上层来看，它有哪些主要特性。在本文中，我们将深入了解真正的基础知识，学习如何使用 JavaScript 最基础的构建单元——变量。

|  |  |
| --- | --- |
| 前提： | 电脑基础知识，了解基本的 HTML 和 CSS，了解 JavaScript 是什么。 |
| 目标： | 熟悉 JavaScript 变量的基础知识。 |

需要的工具
-----

在本文中，你将要输入一些代码来测试你对相关内容的理解。如果你是用的桌面浏览器，输入这些代码最好的地方是浏览器的 JavaScript 控制台（参考什么是浏览器开发工具以查看如何使用这些工具）。

当然，我们也提供了一个简单的 JavaScript 终端，嵌入在下文的页面中，以便你更容易的输入和测试这些代码。

变量是什么？
------

一个变量，就是一个用于存放数值的容器。这个数值可能是一个用于累加计算的数字，或者是一个句子中的字符串。变量的独特之处在于它存放的数值是可以改变的。让我们看一个简单的例子：

```html
<button>Press me</button>
```

```js
const button = document.querySelector("button");

button.onclick = function () {
  let name = prompt("What is your name?");
  alert("Hello " + name + ", nice to see you!");
};
```

在上面的例子中，点击按钮之后，第一行代码会在屏幕上弹出一个对话框，让你输入名字，然后存储输入的名字到一个变量。第二行代码将会显示包含你名字的欢迎信息，你的名字就是从之前的变量里面读取的。

为了理解变量的作用，我们可以思考一下，如果不使用变量，我们实现上述功能的代码将是这样的：
```

let name = prompt('What is your name?');

if (name === 'Adam') {
  alert('Hello Adam, nice to see you!');
} else if (name === 'Alan') {
  alert('Hello Alan, nice to see you!');
} else if (name === 'Bella') {
  alert('Hello Bella, nice to see you!');
} else if (name === 'Bianca') {
  alert('Hello Bianca, nice to see you!');
} else if (name === 'Chris') {
  alert('Hello Chris, nice to see you!');
}

// ... and so on ...
```

你可能暂时还没有完全理解这些代码和语法，但是你应该能够理解到——如果我们没有变量，我们就不得不写大量的代码去枚举和检查输入的名字，然后去显示它们。这样做显然是低效率和不可行的——你没有办法列举出所有可能的输入。

变量的另一个特性就是它们能够存储任何的东西——不只是字符串和数字。变量可以存储更复杂的数据，甚至是函数。你将在后续的内容中体验到这些用法。

我们说，变量是用来存储数值的，那么有一个重要的概念需要区分。变量不是数值本身，它们仅仅是一个用于存储数值的容器。你可以把变量想象成一个个用来装东西的纸箱子。

![](/zh-CN/docs/Learn/JavaScript/First_steps/Variables/boxes.png)

声明变量
----

要想使用变量，你需要做的第一步就是创建它——更准确的说，是声明一个变量。声明一个变量的语法是在 `var` 或 `let` 关键字之后加上这个变量的名字：

```js
let myName;
let myAge;
```

在这里我们声明了两个变量 `myName` 和 `myAge`. 那么现在尝试输入这些代码到你的浏览器终端或者下面提供的 JavaScript 终端 (你也可以在另一个独立的标签页或窗口 open this consol ). 此外，你也可以多声明几个变量。
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>JavaScript console</title>
    <style>
 \* {
 box-sizing: border-box;
 }

 html {
 background-color: #0c323d;
 color: #809089;
 font-family: monospace;
 }

 body {
 max-width: 700px;
 }

 p {
 margin: 0;
 width: 1%;
 padding: 0 1%;
 font-size: 16px;
 line-height: 1.5;
 float: left;
 }

 .input p {
 margin-right: 1%;
 }

 .output p {
 width: 100%;
 }

 .input input {
 width: 96%;
 float: left;
 border: none;
 font-size: 16px;
 line-height: 1.5;
 font-family: monospace;
 padding: 0;
 background: #0c323d;
 color: #809089;
 }

 div {
 clear: both;
 }
 </style>
  </head>
  <body></body>

  <script>
 let geval = eval;
 function createInput() {
 let inputDiv = document.createElement("div");
 let inputPara = document.createElement("p");
 let inputForm = document.createElement("input");

 inputDiv.setAttribute("class", "input");
 inputPara.textContent = ">";
 inputDiv.appendChild(inputPara);
 inputDiv.appendChild(inputForm);
 document.body.appendChild(inputDiv);

 inputForm.addEventListener("change", executeCode);
 }

 function executeCode(e) {
 try {
 let result = geval(e.target.value);
 } catch (e) {
 let result = "error — " + e.message;
 }

 let outputDiv = document.createElement("div");
 let outputPara = document.createElement("p");

 outputDiv.setAttribute("class", "output");
 outputPara.textContent = "Result: " + result;
 outputDiv.appendChild(outputPara);
 document.body.appendChild(outputDiv);

 e.target.disabled = true;
 e.target.parentNode.style.opacity = "0.5";

 createInput();
 }

 createInput();
 </script>
</html>
```

**备注：** 在 JavaScript 中，所有代码指令都会以分号结尾 (`;`) — 如果忘记加分号，你的单行代码可能执行正常，但是在多行代码在一起的时候就可能出错。所以，最好是养成主动以分号作为代码结尾的习惯。

你可以通过使用变量的方式来验证这个变量的数值是否在执行环境中已经存在。例如，

```js
myName;
myAge;
```

以上这两个变量并没有数值，他们是空的容器。当你输入变量名并回车后，你会得到一个`undefined`的返回值。如果他们并不存在的话，那你就会得到一个报错信息。不信的话，可以尝试输入：

```js
scoobyDoo;
```

**备注：** 千万不要把两个概念弄混淆了，“一个变量存在，但是没有数值”和“一个变量并不存在” — 他们完全是两回事 — 在前面你看到的盒子的类比中，不存在意味着没有可以存放变量的“盒子”。没有定义的值意味着有一个“盒子”，但是它里面没有任何值。

初始化变量
-----

一旦你定义了一个变量，你就能够初始化它。方法如下，在变量名之后跟上一个“=”，然后是数值：

```js
myName = "Chris";
myAge = 37;
```

现在回到控制台并且尝试输入这几行。每输入一条你都应该确认一下控制台输出的变量是不是你刚赋的值。同样，你可以通过在控制台中输入变量的名称来返回该变量的值 — 再次尝试下这些：

```js
myName;
myAge;
```

你可以像这样在声明变量的时候给变量初始化：

```js
let myName = "Chris";
```

这可能是大多数时间你都会使用的方式，因为它要比在单独的两行上做两次操作要快。

**备注：** 如果你写一个声明和初始化变量的多行 JavaScript 代码的程序，你可以在初始化变量之后再实际声明它，并且它仍然可以工作。这是因为变量的声明通常在其余的代码执行之前完成。这叫做**顶置**—阅读var hoisting来了解更多细节。

var 与 let 的区别
-------------

此时，你可能会想：“为什么我们需要两个关键字来定义变量？”，“为什么有 `var` 和 `let` 呢？"。

原因是有些历史性的。回到最初创建 JavaScript 时，是只有 `var` 的。在大多数情况下，这种方法可以接受，但有时在工作方式上会有一些问题——它的设计会令人困惑或令人讨厌。因此，`let` 是在现代版本中的 JavaScript 创建的一个新的关键字，用于创建与 `var` 工作方式有些不同的变量，解决了过程中的问题。

下面解释几个简单的差异。我们现在不会讨论所有的差异，但是当你了解有关 JavaScript 的更多信息时，你将开始发现它们（如果你现在真的想要阅读它们，请随时查看我们的参考页面）。

首先，如果你编写一个声明并初始化变量的多行 JavaScript 程序，你可以在初始化一个变量之后用 `var` 声明它，它仍然可以工作。例如：

```js
myName = "Chris";

function logName() {
  console.log(myName);
}

logName();

var myName;
```

**备注：** 只有在 web 文档中运行多行 JavaScript 时才会有这种效果，当在 JavaScript 控制台中键入单独的行，这将不起作用。

这是由于变量的**提升**，更多细节请阅读变量提升。

但提升操作不再适用于 `let` 。如果将上面例子中的 `var` 替换成 `let` 将不起作用并引起一个错误。这是一件好事——因为初始化后再声明一个变量会使代码变得混乱和难以理解。

其次，当你使用 `var` 时，可以根据需要多次声明相同名称的变量，但是 `let` 不能。以下将有效：

```js
var myName = "Chris";
var myName = "Bob";
```

但是以下内容会在第二行引发错误：

```js
let myName = "Chris";
let myName = "Bob";
```

你必须这样做：

```js
let myName = "Chris";
myName = "Bob";
```

同样，这是一个明智的语言决定。没有理由重新声明变量——这只会让事情变得更加混乱。

出于这些以及其他原因，我们建议你在代码中尽可能多地使用 `let`，而不是 `var`。因为没有理由使用 `var`，除非你需要用代码支持旧版本的 Internet Explorer (它直到第 11 版才支持 `let` ，现代的 Windows Edge 浏览器支持的很好)。

**备注：** 我们目前正在更新课程以使用 let 而不是 var。忍耐一下！

更新变量
----

一旦变量赋值，你可以通过简单地给它一个不同的值来更新它。试试在你的控制台中输入下面几行：

```js
myName = "Bob";
myAge = 40;
```

### 关于变量命名的规则

你可以给你的变量赋任何你喜欢的名字，但有一些限制。一般你应当坚持使用拉丁字符 (0-9,a-z,A-Z) 和下划线字符。

* 你不应当使用规则之外的其他字符，因为它们可能引发错误，或对国际用户来说难以理解。
* 变量名不要以下划线开头——以下划线开头的被某些 JavaScript 设计为特殊的含义，因此可能让人迷惑。
* 变量名不要以数字开头。这种行为是不被允许的，并且将引发一个错误。
* 一个可靠的命名约定叫做 "小写驼峰命名法"，用来将多个单词组在一起，小写整个命名的第一个字母然后大写剩下单词的首字符。我们已经在文章中使用了这种命名方法。
* 让变量名直观，它们描述了所包含的数据。不要只使用单一的字母/数字，或者长句。
* 变量名大小写敏感——因此`myage`与`myAge`是 2 个不同的变量。
* 最后也是最重要的一点——你应当避免使用 JavaScript 的保留字给变量命名。保留字，即是组成 JavaScript 的实际语法的单词！因此诸如 `var`、`function`、`let` 和 `for` 等，都不能被作为变量名使用。浏览器将把它们识别为不同的代码项，因此你将得到错误。

**备注：** 你能从词汇语法—关键字找到一个相当完整的保留关键字列表来避免使用关键字当作变量。

好的命名示例：
```

age
myAge
init
initialColor
finalOutputValue
audio1
audio2
```

错误与差的命名示例：
```

1
a
_12
myage
MYAGE
var
Document
skjfndskjfnbdskjfb
thisisareallylongstupidvariablenameman
```

现在尝试创建更多的变量，请将上面的指导铭记于心。

变量类型
----

可以为变量设置不同的数据类型。本节我们将对其进行简短的介绍，在以后的文章中，你会更详细地了解它们。

到目前为止我们已经认识了前 2 个，但是还有其他的。

### Number

你可以在变量中存储数字，不论这些数字是像 30（也叫整数）这样，或者像 2.456 这样的小数（也叫做浮点数）。与其他编程语言不同，在 JavaScript 中你不需要声明一个变量的类型。当你给一个变量数字赋值时，不需要用引号括起来。

```js
let myAge = 17;
```

### String

字符串是文本的一部分。当你给一个变量赋值为字符串时，你需要用单引号或者双引号把值给包起来，否则 JavaScript 将会把这个字符串值理解成别的变量名。

```js
let dolphinGoodbye = "So long and thanks for all the fish";
```

### Boolean

Boolean 的值有 2 种：true 或 false。它们通常被用于在适当的代码之后，测试条件是否成立。举个例子，一个简单的示例如下：

```js
let iAmAlive = true;
```

然而实际上通常是以下用法：

```js
let test = 6 < 3;
```

这是使用“小于”操作符（<）来测试 6 小于 3。正如你所料的，将会返回`false`，因为 6 并不小于 3！在这个课程中，以后你将会学到许多有关操作符的知识。

### Array

数组是一个单个对象，其中包含很多值，方括号括起来，并用逗号分隔。尝试在你的控制台输入以下行：

```js
let myNameArray = ["Chris", "Bob", "Jim"];
let myNumberArray = [10, 15, 40];
```

当数组被定义后，你可以使用如下所示的语法来访问各自的值，例如下行：

```js
myNameArray[0]; // should return 'Chris'
myNumberArray[2]; // should return 40
```

此处的方括号包含一个索引值，该值指定要返回的值的位置。你可能已经注意到，计算机从 0 开始计数，而不是像我们人类那样的 1。

在以后的文章，你将更多地了解数组。

### Object

在编程中，对象是现实生活中的模型的一种代码结构。你可以有一个简单的对象，代表一个停车场，并包含有关其宽度和长度的信息，或者你可以有一个代表一个人的对象，并包含有关他们的名字，身高，体重，他们说什么语言，如何说 你好，他们，等等。

尝试在你的控制台输入以下行：

```js
let dog = { name: "Spot", breed: "Dalmatian" };
```

要检索存储在对象中的信息，可以使用以下语法：

```js
dog.name;
```

我们现在不会看对象了 - 你可以在将来的模块中了解更多关于这些对象的信息。

动态类型
----

JavaScript 是一种“动态类型语言”，这意味着不同于其他一些语言 (译者注：如 C、JAVA)，你不需要指定变量将包含什么数据类型（例如 number 或 string）

例如，如果你声明一个变量并给它一个带引号的值，浏览器就会知道它是一个字符串：

```js
let myString = "Hello";
```

即使它包含数字，但它仍然是一个字符串，所以要小心：

```js
let myNumber = "500"; // oops, this is still a string
typeof myNumber;
myNumber = 500; // much better — now this is a number
typeof myNumber;
```

尝试依次将上述代码输入你的控制台，看看结果是什么（无须输入//之后的注释）。我们使用了一个名为`typeof`的特殊的操作符——它会返回所传递给它的变量的数据类型。第一次在上面的代码中调用它，它应该返回 string，因为此时 myNumber 变量包含一个字符串'500'。看看它第二次将返回什么。

总结
--

到目前为止，你应该了解了一些 JavaScript 变量以及如何创建它们。在下一篇文章中，我们将更详细地关注数字，了解如何在 JavaScript 中使用基础数学。

* 上一页
* Overview: First steps
* 下一页


什么是 JavaScript？ - 学习 Web 开发

什么是 JavaScript？
===============

* Overview: First steps
* 下一页

欢迎来到 MDN 的 JavaScript 初学者课程！本节将在一定高度俯瞰 JavaScript，回答一些诸如“它是什么？”和“它能做什么？”的问题。并使你熟悉 JavaScript 的用途。

|  |  |
| --- | --- |
| 前提： | 计算机基础知识，初步理解 HTML 和 CSS。 |
| 目标： | 初步了解 JavaScript，包括一些概念、用途和嵌入网站的方法。 |

高层定义
----

JavaScript 是一种脚本编程语言，它可以在网页上实现复杂的功能，网页展现给你的不再是简单的静态信息，而是实时的内容更新——交互式的地图、2D/3D 动画、滚动播放的视频等等——JavaScript 就在其中。它是标准 Web 技术蛋糕的第三层，其中 HTML 和 CSS 我们已经在学习区的其他部分进行了详细的讲解。

![标准 web 技术的三层——HTML、CSS 和 JavaScript](/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript/cake.png)

* HTML 是一种标记语言，用来结构化我们的网页内容并赋予内容含义，例如定义段落、标题和数据表，或在页面中嵌入图片和视频。
* CSS 是一种样式规则语言，可将样式应用于 HTML 内容，例如设置背景颜色和字体，在多个列中布局内容。
* JavaScript 是一种脚本语言，可以用来创建动态更新的内容，控制多媒体，制作图像动画，还有很多。（好吧，虽然它不是万能的，但可以通过简短的代码来实现神奇的功能。）

这三层依次建立，秩序井然。以简单文本标签作为示例。首先用 HTML 将文本标记起来，从而赋予它结构和目的：

```html
<p>Player 1: Chris</p>
```

![作为纯文本段落出现的“Player 1: Chris”](/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript/just-html.png)

然后我们可以为它加一点 CSS 让它更好看：

```css
p {
  font-family: "helvetica neue", helvetica, sans-serif;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: center;
  border: 2px solid rgb(0 0 200 / 0.6);
  background: rgb(0 0 200 / 0.6);
  color: rgb(255 255 255 / 1);
  box-shadow: 1px 1px 2px rgb(0 0 200 / 0.4);
  border-radius: 10px;
  padding: 3px 10px;
  display: inline-block;
  cursor: pointer;
}
```

![已添加样式的“Player 1: Chris”段落](/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript/html-and-css.png)

最后，我们可以再加上一些 JavaScript 来实现动态行为：

```js
const para = document.querySelector("p");

para.addEventListener("click", updateName);

function updateName() {
  const name = prompt("Enter a new name");
  para.textContent = `Player 1: ${name}`;
}
```

尝试点击最后一个版本的文本标签，观察会发生什么（也可在 GitHub 上查看这个演示的源代码或在线运行它）！

JavaScript 能做的远不止这些。让我们来仔细探索。

它到底可以做什么？
---------

客户端 JavaScript 语言的核心包含一些普遍的编程特性，以让你可以做到如下的事情：

* 在变量中储存有用的值。比如上文的示例中，我们请求客户输入一个新名字，然后将其储存到 `name` 变量中。
* 操作一段文本（在编程中称为“字符串”（string））。上文的示例中，我们取字符串“玩家 1：”，然后把它和 `name` 变量拼接起来，创造出完整的文本标签，比如“玩家 1：小明”。
* 运行代码以响应网页中发生的特定事件。上文的示例中，我们用一个 `click` 事件来检测按钮什么时候被点击，然后运行代码更新文本标签。
* 以及更多！

JavaScript 语言核心之上所构建的功能更令人兴奋。**应用程序接口**（**Application Programming Interface，API**）将为你的代码提供额外的超能力。

API 是已经建立好的一套代码组件，可以让开发者实现原本很难甚至无法实现的程序。就像现成的家具套件之于家居建设，用一些已经切好的木板组装一个书柜，显然比自己设计，寻找合适的木材，裁切至合适的尺寸和形状，找到正确尺寸的螺钉，*然后*再组装成书柜要简单得多。

API 通常分为两类。

![API 的两种分类：第三方 API 显示于浏览器同侧，浏览器 API 在浏览器内部](/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript/browser.png)

**浏览器 API** 内建于 web 浏览器中，它们可以将数据从周边计算机环境中筛选出来，还可以做实用的复杂工作。例如：

* 文档对象模型 API 能通过创建、移除和修改 HTML，为页面动态应用新样式等手段来操作 HTML 和 CSS。比如当某个页面出现了一个弹窗，或者显示了一些新内容（像上文小演示中看到那样），这就是 DOM 在运行。
* 地理位置 API 获取地理信息。这就是为什么谷歌地图可以找到你的位置，而且标示在地图上。
* 画布（Canvas） 和 WebGL API 可以创建生动的 2D 和 3D 图像。人们正运用这些 web 技术制作令人惊叹的作品。参见 Chrome Experiments 以及 webglsamples。
* 诸如 `HTMLMediaElement` 和 WebRTC 等影音类 API (en-US) 让你可以利用多媒体做一些非常有趣的事，比如在网页中直接播放音乐和影片，或用自己的网络摄像头获取录像，然后在其他人的电脑上展示（试用简易版截图演示以理解这个概念）。

**备注：** 上述很多演示都不能在旧浏览器中运行。推荐你在测试代码时使用诸如 Firefox、Chrome、Edge 或者 Opera 等现代浏览器。当代码即将交付生产环境时（也就是真实的客户即将使用真实的代码时），你还需要深入考虑跨平台测试。

**第三方 API** 并没有默认嵌入浏览器中，一般要从网上取得它们的代码和信息。比如：

* Twitter API、新浪微博 API 可以在网站上展示最新推文之类。
* 谷歌地图 API、OpenStreetMap API、高德地图 API 可以在网站嵌入定制的地图等等。

**备注：** 这些 API 为进阶内容，本模块中不会涉及，更多信息请参考：客户端 web API 模块。

先稳住！你看到的只是冰山一角。你不可能仅靠学一天 JavaScript 就能构建下一个 Facebook、谷歌地图、或 Instagram——还有很多基础需要了解，这也是为什么你会在这里，让我们继续吧！

JavaScript 在页面上做了什么？
--------------------

现在我们实实在在的学习一些代码，与此同时，探索 JavaScript 运行时背后发生的事情。

让我们简单回顾一下，浏览器在读取一个网页时都发生什么（CSS 如何工作一文中首次谈及）。浏览器在读取一个网页时，代码（HTML、CSS 和 JavaScript）将在一个运行环境（浏览器标签页）中得到执行。就像一间工厂，将原材料（代码）加工为一件产品（网页）。

![当页面加载时，HTML、CSS 和 JavaScript 代码共同创建浏览器标签页中的内容](/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript/execution.png)

JavaScript 的一个非常常见的用途是通过文档对象模型 API（如上所述）动态修改 HTML 和 CSS，以更新用户界面。请注意，网页文档中的代码通常按照其在页面上出现的顺序加载和执行。如果 JavaScript 先于要修改的 HTML 和 CSS 加载和运行，则可能发生错误。你将在本文后面的脚本加载策略部分了解到解决这个问题的方法。

### 浏览器安全

每个浏览器标签页就是其自身用来运行代码的独立容器（这些容器用专业术语称为“运行环境”）。大多数情况下，每个标签页中的代码完全独立运行，而且一个标签页中的代码不能直接影响另一个标签页（或者另一个网站）中的代码。这是一个好的安全措施，如果不这样，黑客就可以从其他网站盗取信息，或做一些其他坏事。

**备注：** 以安全的方式在不同网站或标签页中传送代码和数据的方法是存在的，但它们属于进阶技术，本课程不会涉及。

### JavaScript 运行次序

当浏览器执行到一段 JavaScript 代码时，通常会按从上往下的顺序执行这段代码。这意味着你需要注意代码书写的顺序。比如，我们回到第一个例子中的 JavaScript 代码：

```js
const para = document.querySelector("p");

para.addEventListener("click", updateName);

function updateName() {
  const name = prompt("输入一个新的名字：");
  para.textContent = `玩家 1：${name}`;
}
```

这里我们选定一个文本段落（第 1 行)，然后给它附上一个事件监听器（第 3 行），使得在它被点击时，`updateName()` 代码块（5 – 8 行）便会运行。`updateName()` 代码块（这类可以重复使用的代码块称为“函数”）向用户请求一个新名字，然后把这个名字插入到段落中以更新显示。

如果你互换了代码里最初两行的顺序，会导致问题。浏览器开发者控制台将返回一个错误：`TypeError: para is undefined`。这意味着 `para` 对象还不存在，所以我们不能为它增添一个事件监听器。

**备注：** 这是一个很常见的错误，在引用对象之前必须确保该对象已经存在。

### 解释代码 vs 编译代码

作为程序员，你或许听说过这两个术语：**解释**（interpret）和**编译**（compile）。在解释型语言中，代码自上而下运行，且实时返回运行结果。代码在由浏览器执行前，不需要将其转化为其他形式。代码将直接以文本格式被接收和处理。

相对的，编译型语言需要先将代码转化（编译）成另一种形式才能运行。比如 C/C++ 先被编译成机器码，然后才能由计算机运行。程序将以二进制的格式运行，这些二进制内容是由程序源代码产生的。

JavaScript 是轻量级解释型语言。浏览器接受到 JavaScript 代码，并以代码自身的文本格式运行它。技术上，几乎所有 JavaScript 转换器都运用了一种叫做**即时编译**（just-in-time compiling）的技术；当 JavaScript 源代码被执行时，它会被编译成二进制的格式，使代码运行速度更快。尽管如此，JavaScript 仍然是一门解释型语言，因为编译过程发生在代码运行中，而非之前。

两种类型的语言各有优势，这个问题我们暂且不谈。

### 服务器端代码 vs 客户端代码

你或许还听说过**服务器端**（server-side）和**客户端**（client-side）代码这两个术语，尤其是在 web 开发时。客户端代码是在用户的电脑上运行的代码，在浏览一个网页时，它的客户端代码就会被下载，然后由浏览器来运行并展示。在本模块中我们讨论的主要是**客户端 JavaScript**。

而服务器端代码在服务器上运行，然后运行结果才由浏览器下载并展示出来。流行的服务器端 web 语言包括：PHP、Python、Ruby、ASP.NET，甚至有 JavaScript！JavaScript 也可用作服务器端语言，比如现在流行的 Node.js 环境，你可以在我们的动态网页——服务器端编程主题中找到更多关于服务器端 JavaScript 的知识。

### 动态代码 vs 静态代码

**动态**一词既适用于客户端 JavaScript，又适用于描述服务器端语言。它是指通过按需生成新内容来更新 web 页面或应用，使得不同环境下可以显示出不同内容。服务器端代码会在服务器上动态生成新内容，例如从数据库中提取信息。而客户端 JavaScript 则在用户端浏览器中动态生成新内容，例如创建一个新的 HTML 表格，用从服务器请求到的数据填充，然后在网页中向用户展示这个表格。两种情况的意义略有不同，但又有所关联，且两者（服务器端和客户端）经常协同工作。

没有动态更新内容的网页叫做**静态**页面，所显示的内容不会改变。

怎样向页面添加 JavaScript？
-------------------

可以像添加 CSS 那样将 JavaScript 添加到 HTML 页面中。CSS 使用 `<link>` 元素链接外部样式表，使用 `<style>` 元素向 HTML 嵌入内部样式表，而 JavaScript 这里只需一个元素——`<script>`。我们来看看它是怎么工作的。

### 内部 JavaScript

1. 首先，下载示例文件 apply-javascript.html。放在一个方便的文件夹里。
2. 分别在浏览器和文本编辑器中打开这个文件。你会看到这个 HTML 文件创建了一个简单的网页，其中有一个可点击按钮。
3. 然后转到文本编辑器，在 `</head>` 标签结束前插入以下代码：

```html
<script>
 // 在此编写 JavaScript 代码
</script>
```

4. 下面，在 `<script>` 元素中添加一些 JavaScript 代码，这个页面就能做一些更有趣的事。在“// 在此编写 JavaScript 代码”一行下方添加以下代码：

```js
document.addEventListener("DOMContentLoaded", () => {
  function createParagraph() {
    const para = document.createElement("p");
    para.textContent = "你点击了按钮！";
    document.body.appendChild(para);
  }

  const buttons = document.querySelectorAll("button");

  for (const button of buttons) {
    button.addEventListener("click", createParagraph);
  }
});
```

5. 保存文件并刷新浏览器，然后你会发现，点击按钮文档下方将会添加一个新段落。

**备注：** 如果示例不能正常工作，请依次检查所有步骤，并保证没有纰漏。原始文件是否以 `.html` 为扩展名保存到本地了？`</body>` 标签前是否添加了 `<script>` 元素？JavaScript 代码输入是否正确？**JavaScript 是区分大小写的，而且非常精确，所以你需要准确无误地输入所示的句法，否则可能会出错。**

**备注：** 你可以在 GitHub 上查看此版本 apply-javascript-internal.html（也可在线查看）。

### 外部 JavaScript

这很不错，但是能不能把 JavaScript 代码放置在一个外部文件中呢？现在我们来研究一下。

1. 首先，在刚才的 HTML 文件所在的目录下创建一个名为 `script.js` 的新文件。请确保扩展名为 `.js`，只有这样才能被识别为 JavaScript 代码。
2. 将 `<script>` 元素替换为：

```html
<script src="script.js" defer></script>
```

3. 在 `script.js` 文件中，添加下面的脚本：

```js
function createParagraph() {
  const para = document.createElement("p");
  para.textContent = "你点击了按钮！";
  document.body.appendChild(para);
}

const buttons = document.querySelectorAll("button");

for (const button of buttons) {
  button.addEventListener("click", createParagraph);
}
```

4. 保存并刷新浏览器，你会发现二者完全一样！区别在于，现在我们把 JavaScript 写进了一个外部文件。这样做一般会使代码更加有序，更易于复用，且没有了脚本的混合，HTML 也会更加易读，因此这是个好的习惯。

**备注：** 你可以在 GitHub 上查看 apply-javascript-external.html 以及 script.js（也可在线查看）。

### 内联 JavaScript 处理器

注意，有时候你会遇到在 HTML 中存在着一丝真实的 JavaScript 代码。它或许看上去像这样：

```js
function createParagraph() {
  const para = document.createElement("p");
  para.textContent = "你点击了按钮！";
  document.body.appendChild(para);
}
```

```html
<button onclick="createParagraph()">点我！</button>
```

你可以在下面尝试这个版本的演示。

这个演示与之前的两个功能完全一致，只是在 `<button>` 元素中包含了一个内联的 `onclick` 处理器，使得函数在按钮被按下时运行。

**然而请不要这样做**。这将使 JavaScript 污染了 HTML，而且效率低下。对于每个需要应用 JavaScript 的按钮，你都得手动添加 `onclick="createParagraph()"` 属性。

### 请使用 addEventListener

与其在 HTML 中包含 JavaScript，不如使用纯 JavaScript 构造。通过 `querySelectorAll()` 函数，可以选择页面上的所有按钮。然后可以循环遍历这些按钮，使用 `addEventListener()` 为每个按钮分配一个处理器。代码如下所示：

```js
const buttons = document.querySelectorAll("button");

for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", createParagraph);
}
```

这样写乍看去比 `onclick` 属性要长一些，但是这样写会对页面上所有按钮生效，无论多少个，或添加或删除，完全无需修改 JavaScript 代码。

**备注：** 请尝试修改 `apply-javascript.html` 以添加更多按钮。刷新后可发现按下任一按钮时都会创建一个段落。这样很高效吧？

### 脚本调用策略

要让脚本调用的时机符合预期，需要解决一系列的问题。这里看似简单，实际大有文章。最常见的问题就是：HTML 元素是按其在页面中出现的次序调用的，如果用 JavaScript 来管理页面上的元素（更精确的说法是使用文档对象模型），若 JavaScript 加载于欲操作的 HTML 元素之前，则代码将出错。

在上文的“内部”、“外部”示例中，JavaScript 在文档头部，解析 HTML 文档体之前加载并执行。这样做是有隐患的，需要使用一些结构来避免错误发生。

“内部”示例使用了以下结构：

```js
document.addEventListener("DOMContentLoaded", () => {
  // …
});
```

这是一个事件监听器，它监听浏览器的 `DOMContentLoaded` 事件，其标志了 HTML 文档体完全加载和解析。该代码块中的 JavaScript 在事件被触发后才会运行，因此避免了错误（你将在课程的后面学习事件中了解到这些）。

“外部”示例中使用了 JavaScript 的一项现代技术（`defer` 属性）来解决这一问题，它告知浏览器在遇到 `<script>` 元素时继续下载 HTML 内容。

```html
<script src="script.js" defer></script>
```

上述情况下，脚本和 HTML 将一并加载，代码将顺利运行。

**备注：** 在外部示例中，我们不需要使用 `DOMContentLoaded` 事件，因为 `defer` 属性为我们解决了这个问题。我们没有在内部 JavaScript 示例中使用 `defer` 解决方案，因为 `defer` 只适用于外部脚本。

解决此问题的旧方法是：把脚本元素放在文档体的底端（也就是 `</body>` 标签之前，与之相邻），这样脚本就可以在 HTML 解析完毕后加载了。此方案的问题是：只有在所有 HTML DOM 加载完成后才开始脚本的加载/解析过程。对于有大量 JavaScript 代码的大型网站，可能会带来显著的性能损耗。

#### async 和 defer

上述的脚本阻塞问题实际有两种解决方案——`async` 和 `defer`。我们来依次讲解。

浏览器遇到 `async` 脚本时不会阻塞页面渲染，而是直接下载然后运行。但是，一旦下载完成，脚本就会执行，从而阻止页面渲染。脚本的运行次序无法控制。当页面的脚本之间彼此独立，且不依赖于本页面的其他任何脚本时，`async` 是最理想的选择。

使用 `defer` 属性加载的脚本将按照它们在页面上出现的顺序加载。在页面内容全部加载完毕之前，脚本不会运行，如果脚本依赖于 DOM 的存在（例如，脚本修改了页面上的一个或多个元素），这一点非常有用。

以下是不同脚本加载方法的可视化表示，以及这对页面意味着什么：

![三种脚本加载方法的工作原理：默认情况下，在获取和执行 JavaScript 时，解析过程被阻塞。使用 async 时，解析暂停，仅执行。使用 defer 时，解析不会暂停，但在解析完所有其他内容后才开始执行](/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript/async-defer.jpg)

*该图片来自 HTML spec，经过了复制和裁剪，以 CC BY 4.0 获得授权。*

比如，如果你的页面要加载以下三个脚本：

```html
<script async src="js/vendor/jquery.js"></script>

<script async src="js/script2.js"></script>

<script async src="js/script3.js"></script>
```

三者的调用顺序是不确定的。`jquery.js` 可能在 `script2` 和 `script3` 之前或之后调用，如果这样，后两个脚本中依赖 `jquery` 的函数将产生错误，因为脚本运行时 `jquery` 尚未加载。

`async` 应该在有大量后台脚本需要加载，并且只想尽快加载到位的情况下使用。例如，你可能需要加载一些游戏数据文件，这在游戏真正开始时是需要的，但现在你只想显示游戏介绍、标题和大厅，而不想被脚本加载阻塞。

解决这一问题可使用 `defer` 属性，脚本将按照在页面中出现的顺序加载和运行：

```html
<script defer src="js/vendor/jquery.js"></script>

<script defer src="js/script2.js"></script>

<script defer src="js/script3.js"></script>
```

添加 `defer` 属性的脚本将按照在页面中出现的顺序加载，因此第二个示例可确保 `jquery.js` 必定加载于 `script2.js` 和 `script3.js` 之前，同时 `script2.js` 必定加载于 `script3.js` 之前。在页面内容全部加载完成之前，它们不会运行，如果你的脚本依赖于 DOM 的存在（例如，它们修改了页面上的一个或多个元素），这将非常有用。

脚本调用策略小结：

* `async` 和 `defer` 都指示浏览器在一个单独的线程中下载脚本，而页面的其他部分（DOM 等）正在下载，因此在获取过程中页面加载不会被阻塞。
* `async` 属性的脚本将在下载完成后立即执行。这将阻塞页面，并不保证任何特定的执行顺序。
* 带有 `defer` 属性的脚本将按照它们的顺序加载，并且只有在所有脚本加载完毕后才会执行。
* 如果脚本无需等待页面解析，且无依赖独立运行，那么应使用 `async`。
* 如果脚本需要等待页面解析，且依赖于其他脚本，调用这些脚本时应使用 `defer`，将关联的脚本按所需顺序置于 HTML 的相应 `<script>` 元素中。
注释
--

就像 HTML 和 CSS，JavaScript 代码中也可以添加注释，浏览器会忽略它们，注释只是为你的同事（还有你，如果半年后再看自己写的代码，还会记得其中的含义吗）提供关于代码如何工作的指引。注释非常有用，而且应该经常使用，尤其在大型应用中。注释分为两类：

* 在双斜杠（//）后添加单行注释，比如：

```js
// 我是一条注释
```

* 在 `/*` 和 `*/` 之间添加多行注释，比如：

```js
/\*
 我也是
 一条注释
\*/
```

比如说，我们可以这样为上一个演示添加注释：

```js
// 函数：创建一个新的段落并添加至 HTML body 底部。
function createParagraph() {
  const para = document.createElement("p");
  para.textContent = "你点了这个按钮！";
  document.body.appendChild(para);
}

/\*
 1. 取得页面上所有按钮的引用并将它们置于一个数组中。
 2. 通过一个循环为每个按钮添加一个点击事件的监听器。
 当按钮被点击时，调用 createParagraph() 函数。
\*/

const buttons = document.querySelectorAll("button");

for (const button of buttons) {
  button.addEventListener("click", createParagraph);
}
```

**备注：** 一般来说，注释越多越好，但如果你发现自己添加了大量注释来解释变量是什么（变量名也许应该更直观），或者解释非常简单的操作（也许代码过于复杂），那么就应该小心了。

总结
--

恭喜你，迈出了探索 JavaScript 世界的第一步。我们从理论开始，介绍为什么要使用 JavaScript，以及用它能做什么事情。过程中穿插了一些代码示例并讲解了 JavaScript 如何与网站中其他代码适配，等等。

现在 JavaScript 或许还有些令人生畏，但不用担心。在课程中我们会循序渐进。下一节将全力投入实战，让你专注其中，并建立自己的 JavaScript 示例。

* Overview: First steps
* 下一页


JavaScript 对象基础 - 学习 Web 开发

JavaScript 对象基础
===============

* Overview: Objects
* 下一页

在这学习 JavaScript 的对象的首篇文章中，我们将会学习有关对象基础的语法，并且回顾一些之前学过的 JavaScript 的一些特点，使你明白你所使用过的一些功能实际上是由对象提供的。

|  |  |
| --- | --- |
| 前提： |
 基础计算机基础，了解基础的 HTML 和 CSS，熟悉 JavaScript 基础（参见
 JavaScript 第一步和创建代码块）。
  |
| 目标： | 理解在 JavaScript 使用对象的基础知识：创建对象，访问和修改对象属性，以及使用构造函数。 |

对象基础
----

对象是一个包含相关数据和方法的集合（通常由一些变量和函数组成，我们称之为对象里面的属性和方法），让我们通过一个例子来了解它们。

首先，将 oojs.html 文件复制到本地，文件的元素非常少——仅一个供我们编写源代码的 `<script>` 标签。我们将在此基础上研究对象基础语法。在使用此示例时，你应该打开开发者工具 JavaScript 控制台，并准备键入一些命令。

如同 JavaScript 中的很多东西一样，创建一个对象通常先定义并初始化变量。尝试在你的文件中输入以下 JavaScript 代码，保存并刷新页面：

```js
const person = {};
```

打开你的浏览器的 JavaScript 控制台输入 `person`，然后按下 `Enter`/`Return`，你可能会得到以下结果中的一种：
```

[object Object]
Object { }
{ }
```

恭喜，你刚创建了你的第一个对象。干的漂亮！但这是一个空对象，所以我们做不了很多事情。让我们像这样更新文件中的 JavaScript 对象：

```js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio: function () {
    console.log(`${this.name[0]} ${this.name[1]} 现在 ${this.age} 岁了。`);
  },
  introduceSelf: function () {
    console.log(`你好！我是 ${this.name[0]}。`);
  },
};
```

保存并刷新后，尝试在你的浏览器开发者工具的 JavaScript 控制台输入以下内容：

```js
person.name;
person.name[0];
person.age;
person.bio();
// "Bob Smith 现在 32 岁了。"
person.introduceSelf();
// "你好！我是 Bob。"
```

现在，你的对象拥有了一些数据和功能，你现在可以通过简单的语法访问它们了！

所以发生了什么？一个对象由许多的成员组成，每一个成员都拥有一个名字（如 `name`、`age`）和一个值（如 `['Bob', 'Smith']`、`32`）。每一组名字/值必须由逗号分隔开，并且名字和值之间由冒号分隔，语法规则如下所示：

```js
const objectName = {
  member1Name: member1Value,
  member2Name: member2Value,
  member3Name: member3Value,
};
```

对象成员的值可以是任意的，person 对象里包含了：一个数字、一个数组，以及两个函数。前两项是数据项，被称为对象的**属性**，后两项是允许对象对该数据进行某些操作的函数，称为对象的**方法**。

当对象的成员是函数时，语法会更简单。我们可以写 `bio()` 来代替 `bio: function()`。像这样：

```js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio() {
    console.log(`${this.name[0]} ${this.name[1]} 现在 ${this.age} 岁了。`);
  },
  introduceSelf() {
    console.log(`你好！我是 ${this.name[0]}。`);
  },
};
```

从现在开始，我们将使用这种更短的语法。

一个如上所示的对象被称之为对象字面量（object literal）——手动的写出对象的内容来创建一个对象。不同于从类实例化一个对象，我们会在后面学习这种方式。

当你想要传输一系列结构化的相关的数据项（例如，服务器发起请求以存储一些数据到数据库）时，常见的方式是使用字面量来创建一个对象。发送一个对象要比分别发送这些数据更有效率，当你使用名字标识这些数据时，它比数组更容易使用。

点表示法
----

在上面的例子中，你使用了**点表示法**（dot notation）来访问对象的属性和方法。对象的名字表现为一个**命名空间**（namespace）。当你想访问对象内部的属性或方法时，命名空间必须写在第一位。然后输入一个点，紧接着是你想要访问的目标——可以是简单属性的名字，或者是数组属性的一个子元素，又或者是对象的方法调用。如下所示：

```js
person.age;
person.bio();
```

### 子命名空间

可以用一个对象来做另一个对象成员的值。例如将 `name` 成员，从

```js
const person = {
  name: ["Bob", "Smith"],
};
```

改成

```js
const person = {
  name: {
    first: "Bob",
    last: "Smith",
  },
  // …
};
```

需要访问这些属性，只需要链式的再使用一次点表示法，像这样：

```js
person.name.first;
person.name.last;
```

**注意**：你需要改变你之前的代码，从

```js
name[0];
name[1];
```

改成

```js
name.first;
name.last;
```

否则，你的方法将不再有效。

括号表示法
-----

另外一种访问对象属性的方式是使用括号表示法（bracket notation），下方所示的点表示法：

```js
person.age;
person.name.first;
```

可以使用如下所示的括号表示法替代：

```js
person["age"];
person["name"]["first"];
```

这看起来很像访问一个数组的元素，并且基本上是相同的——使用关联了值的名称，而不是索引来选择元素。因此对象有时被称为**关联数组**——对象将字符串映射到值，而数组将数字映射到值。

点表示法通常优于括号表示法，因为它更简洁且更易于阅读。然而，在某些情况下你必须使用括号。例如，如果对象属性名称保存在变量中，则不能使用点表示法访问该值，但可以使用括号表示法访问该值。

在下面的示例中，`logProperty()` 函数可以使用 `person[propertyName]` 来检索 `propertyName` 中指定的属性的值。

```js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
};

function logProperty(propertyName) {
  console.log(person[propertyName]);
}

logProperty("name");
// ["Bob", "Smith"]
logProperty("age");
// 32
```

设置对象成员
------

目前我们仅仅看到了如何查询（或**获取**）对象成员，而你也可以通过声明**设置**（更新）对象成员的值（使用点表示法或括号表示法），像这样：

```js
person.age = 45;
person["name"]["last"] = "Cratchit";
```

尝试输入以上代码，然后再查看这些成员是否已经被改变了，像这样：

```js
person.age;
person["name"]["last"];
```

设置成员并不意味着你只能更新已经存在的属性的值和方法，你也可以创建新的成员。在 JS 控制台中尝试以下代码：

```js
person["eyes"] = "hazel";
person.farewell = function () {
  console.log("再见！");
};
```

现在你可以测试你新创建的成员

```js
person["eyes"];
person.farewell();
// "再见！"
```

括号表示法一个有用的地方是它不仅可以动态的去设置对象成员的值，还可以动态的去设置成员的名字。假设我们想让用户能够通过在两个文本输入框中键入成员名称和值，在他们的人员数据中存储自定义的值类型。我们可以像这样获取这些值：

```js
const myDataName = nameInput.value;
const myDataValue = nameValue.value;
```

我们可以像这样把这个新的成员的名字和值加到 `person` 对象中：

```js
person[myDataName] = myDataValue;
```

为了测试它，尝试在你的代码里添加以下几行，就在 `person` 对象的右花括号的下面：

```js
const myDataName = "height";
const myDataValue = "1.75m";
person[myDataName] = myDataValue;
```

现在，保存并刷新页面，在输入框里输入以下代码：

```js
person.height;
```

这是使用点表示法无法做到的，点表示法只能接受字面量的成员的名字，不接受表示名称的变量。

“this”的含义
---------

你也许在我们的方法里注意到了一些奇怪的地方，看这个例子：

```js
introduceSelf() {
  console.log(`你好！我是 ${this.name[0]}。`);
}
```

你也许想知道“this”是什么，关键字 `this` 指向了当前代码运行时的对象——这里指 `person` 对象，为什么不直接写 `person` 呢？

当你只需要创建一个对象字面量时，`this` 就不是那么有用。但是如果你创建多个对象时，`this` 可以让你对每一个创建的对象都使用相同的方法定义。

让我们用两个简单的 person 对象来说明：

```js
const person1 = {
  name: "Chris",
  introduceSelf() {
    console.log(`你好！我是 ${this.name}。`);
  },
};

const person2 = {
  name: "Deepti",
  introduceSelf() {
    console.log(`你好！我是 ${this.name}。`);
  },
};
```

在本例中，尽管两个实例的方法代码完全相同，但 `person1.introduceSelf()` 输出“你好！我是 Chris。”，而 `person2.introduceSelf()` 输出“你好！我是 Deepti”。当你手工编写对象字面量时，这并不是很有用，但是当我们开始使用**构造函数**从单个对象定义创建多个对象时，这将是必不可少的，这就是下一节的主题。

构造函数介绍
------

使用对象字面量在只需要创建一个对象时是可以的，但如果你需要创建多个对象，如前面章节所示，它们就不够用了。我们必须为每个创建的对象编写相同的代码，如果我们想要更改对象的某些属性，比如添加一个 `height` 属性，那么我们必须手动更新每个对象。

我们希望有一种方法来定义对象的“特征”，即方法集和属性集，然后可以创建任意多个对象，只需更新不同属性的值。

第一个版本是一个函数：

```js
function createPerson(name) {
  const obj = {};
  obj.name = name;
  obj.introduceSelf = function () {
    console.log(`你好！我是 ${this.name}。`);
  };
  return obj;
}
```

每次调用 `createPerson()` 函数时，它都会创建并返回一个新对象。该对象将具有两个成员：

* 一个 `name` 属性
* 一个 `introduceSelf()` 方法。

请注意，`createPerson()` 接受一个参数 `name` 来设置 `name` 属性的值，但是 `introduceSelf()` 方法的值对于使用此函数创建的所有对象都是相同的。这是创建对象的一种非常常见的模式。

现在我们可以创建任意多个对象，重用该定义：

```js
const salva = createPerson("Salva");
salva.name;
salva.introduceSelf();
// "你好！我是 Salva。"

const frankie = createPerson("Frankie");
frankie.name;
frankie.introduceSelf();
// "你好！我是 Frankie。"
```

这样可以正常工作，但有点冗长：我们必须创建一个空对象，初始化它，并返回它。更好的方法是使用**构造函数**。构造函数只是使用 `new` 关键字调用的函数。当你调用构造函数时，它将：

* 创建一个新对象
* 将 `this` 绑定到新对象，以便你可以在构造函数代码中引用 `this`
* 运行构造函数中的代码
* 返回新对象

按照惯例，构造函数以大写字母开头，并且以它们创建的对象类型命名。因此，可以将我们的示例重写如下：

```js
function Person(name) {
  this.name = name;
  this.introduceSelf = function () {
    console.log(`你好！我是 ${this.name}。`);
  };
}
```

要将 `Person()` 作为构造函数调用，我们使用 `new`：

```js
const salva = new Person("Salva");
salva.name;
salva.introduceSelf();
// "你好！我是 Salva。"

const frankie = new Person("Frankie");
frankie.name;
frankie.introduceSelf();
// "你好！我是 Frankie。"
```

你一直在使用对象
--------

当你使用过这些例子之后，你可能会发现你对点表示法并不陌生，这是因为我们在这个课程里一直在使用它！每次我们学习的示例使用浏览器内建的 API 和 JavaScript 的一些对象时，我们就在使用对象，因为，这些功能是由跟我们所看到的对象同样的结构来构建的，尽管比我们自己定义的示例要复杂许多。

所以当我们这样使用字符串的方法时：

```js
myString.split(",");
```

你正在使用一个 `String` 对象上的方法。每当在代码中创建字符串时，该字符串都会被自动创建为 `String` 的实例，因此会有一些常见的方法和属性。

当你这样访问 document 对象时：

```js
const myDiv = document.createElement("div");
const myVideo = document.querySelector("video");
```

你正在使用 `Document` 对象上的方法。每个页面在加载完毕后，会有一个 `Document` 的实例被创建，叫做 `document`，它代表了整个页面的结构，内容和一些功能，比如页面的 URL。同样的，这意味 `document` 有一些可用的方法和属性。

这同样适用于你使用过的几乎所有其他的内置对象或 API——`Array`、`Math`，等。

请注意内置对象或 API 并不总是自动地创建对象的实例，举例来说，通知 API——允许浏览器发起系统通知，需要你为每一个你想发起的通知都使用构造函数进行实例化。尝试在 JavaScript 控制台里输入以下代码：

```js
const myNotification = new Notification("你好！");
```

技能测试！
-----

你已经读到这篇文章的末尾了，但是你还记得最重要的知识吗？进行下一步之前，你可以找到一些进一步的测试来验证你已经记住了这些知识——参见 “技能测试：对象基础” (en-US)。

总结
--

恭喜，你已经阅读到了我们有关 JavaScript 对象的第一篇文章的末尾，你现在应该对如何在 JavaScript 中使用对象有了很好的认识，包括你自己创建一个简单的对象。你应该清楚对象有利于存储一些相关联的数据和函数，如果你尝试以分开的方式去保存 `person` 对象包含的所有的属性和方法，这是令人沮丧且效率低下的，而且会有很多的变量和函数之间同名的风险。对象使我们将一些信息安全地锁在了它们自己的包内，防止它们被损坏。

在下一篇文章，我们将讨论**原型**（prototype），这是 JavaScript 让对象从其他对象继承属性的基本方式。

* Overview: Objects
* 下一页


JavaScript 中的类 - 学习 Web 开发

JavaScript 中的类
==============

* 上一页
* Overview: Objects
* 下一页

在上一篇文章中，我们介绍了面向对象编程（OOP）的基本概念，并介绍了一个使用 OOP 原则对学校的教授和学生进行建模的示例。

我们还讨论了如何使用原型和构造函数来实现这样的模型，而 JavaScript 还提供了更接近经典 OOP 概念的特性。

在本文中，我们将介绍这些特性。注意，这里描述的特性并不是一种继承对象的新方式：在引擎的底层，这一特性使用的仍是原型。这只是一种更容易的创建原型链的方法。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机素养，对 HTML 和 CSS 有基本的理解，熟悉 JavaScript
 基础（参见第一步和创建代码块）以及面向对象的
 JavaScript（OOJS）基础（参见
 JavaScript 对象基础、对象原型和面向对象编程）。
  |
| 目标： | 理解如何使用 JavaScript 提供的特性实现“经典”的面向对象编程。 |

类和构造函数
------

你可以使用 `class` 关键字声明一个类。下面是上一篇文章中关于 `Person` 类的一个声明：

```js
class Person {
  name;

  constructor(name) {
    this.name = name;
  }

  introduceSelf() {
    console.log(`Hi! I'm ${this.name}`);
  }
}
```

在这个 `Person` 类的声明中，有：

* 一个 `name` 属性。
* 一个需要 `name` 参数的构造函数，这一参数用于初始化新的对象的 `name` 属性。
* 一个 `introduceSelf()` 方法，使用 `this` 引用了对象的属性。

`name;` 这一声明是可选的：你可以省略它，因为在构造函数中的 `this.name = name;` 这行代码会在初始化 `name` 属性前自动创建它。但是，在类声明中明确列出属性可以方便阅读代码的人更容易确定哪些属性是这个类的一部分。

你也可以在声明属性时，为其初始化一个默认值。就像这样：`name = '';`。

构造函数使用 `constructor` 关键字来声明。就像在类声明外的构造函数一样，它会：

* 创建一个新的对象
* 将 `this` 绑定到这个新的对象，你可以在构造函数代码中使用 `this` 来引用它
* 执行构造函数中的代码
* 返回这个新的对象

如上文中给出的类声明的代码，你可以像这样创建和使用一个新的 `Person` 实例：

```js
const giles = new Person("Giles");

giles.introduceSelf(); // Hi! I'm Giles
```

注意，我们使用类的名字来调用构造函数，即示例中的 `Person`。

### 省略构造函数

如果你不需要任何特殊的初始化内容，你可以省略构造函数，默认的构造函数会被自动生成：

```js
class Animal {
  sleep() {
    console.log("zzzzzzz");
  }
}

const spot = new Animal();

spot.sleep(); // 'zzzzzzz'
```

继承
--

对于上文给出的 `Person` 类，我们声明一个它的子类 `Professor`。

```js
class Professor extends Person {
  teaches;

  constructor(name, teaches) {
    super(name);
    this.teaches = teaches;
  }

  introduceSelf() {
    console.log(
      `My name is ${this.name}, and I will be your ${this.teaches} professor.`,
    );
  }

  grade(paper) {
    const grade = Math.floor(Math.random() \* (5 - 1) + 1);
    console.log(grade);
  }
}
```

我们使用 `extends` 关键字来声明这个类继承自另一个类。

我们为 `Professor` 类添加了一个新的属性 `teaches`，就像声明的那样。

因为我们想在创建新的 `Professor` 时设置 `teaches`，我们需要声明一个需要 `name` 和 `teaches` 参数的构造函数。构造函数中需要做的第一件事是使用 `super()` 调用父类的构造函数，并传递 `name` 参数。父类的构造函数会设置 `name` 属性。然后 `Professor` 的构造函数接着设置 `teaches` 属性。

**备注：** 如果子类有任何自己的初始化内容需要完成，它也**必须**先使用 `super()` 来调用父类的构造函数，并传递父类构造函数期望的任何参数。

我们还覆盖了父类的 `introduceSelf()` 方法，并添加了一个新的方法 `grade()`，来为论文打分（我们的教授不是很好，只是随意地为论文打分）。

有了这个声明，我们现在可以创建和使用 professor 实例了：

```js
const walsh = new Professor("Walsh", "Psychology");
walsh.introduceSelf(); // 'My name is Walsh, and I will be your Psychology professor'

walsh.grade("my paper"); // some random grade
```

封装
--

最后，让我们了解一下 JavaScript 中如何实现封装。在上一篇文章中，我们讨论了我们为什么想要使得 `Student` 的 `year` 属性变为私有的，我们可以在不破坏任何使用了 `Student` 类的代码的情况下，修改射箭课程的规则。

这里，就像我们之前想要的那样，声明了 `Student` 类：

```js
class Student extends Person {
  #year;

  constructor(name, year) {
    super(name);
    this.#year = year;
  }

  introduceSelf() {
    console.log(`Hi! I'm ${this.name}, and I'm in year ${this.#year}.`);
  }

  canStudyArchery() {
    return this.#year > 1;
  }
}
```

在这个类的声明中，`#year` 是一个私有数据属性。我们可以构造一个 `Student` 对象，然后在内部使用 `#year`，但如果在类的外部尝试访问 `#year`，浏览器将会抛出错误：

```js
const summers = new Student("Summers", 2);

summers.introduceSelf(); // Hi! I'm Summers, and I'm in year 2.
summers.canStudyArchery(); // true

summers.#year; // SyntaxError
```

私有数据属性必须在类的声明中声明，而且其名称需以 `#` 开头。

### 私有方法

与私有数据属性一样，你也可以声明私有方法。而且名称也是以 `#` 开头，只能在类自己的方法中调用：

```js
class Example {
  somePublicMethod() {
    this.#somePrivateMethod();
  }

  #somePrivateMethod() {
    console.log("You called me?");
  }
}

const myExample = new Example();

myExample.somePublicMethod(); // 'You called me?'

myExample.#somePrivateMethod(); // SyntaxError
```

技能测试！
-----

你已经看到了本文的结尾，但你还记得本文的绝大多数重要的信息吗？在继续学习新的内容之前，你可以找一些进一步的测验来验证你已经掌握了这些信息——参见技能测试：面向对象的 JavaScript。

总结
--

本文中，我们介绍了 JavaScript 中可用的面向对象编程的主要工具。我们没有涵盖所有的内容，但这些应该足以让你入门。我们的关于类的文章可让你进一步学习相关的知识。


对象原型 - 学习 Web 开发

对象原型
====

* 上一页
* Overview: Objects
* 下一页

原型是 JavaScript 对象相互继承特性的机制。在这篇文章中，我们将解释什么是原型，原型链如何工作，以及如何为一个对象设置原型。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机素养，对 HTML 和 CSS 有基本的理解，熟悉 JavaScript 基础（参见
 JavaScript 第一步和创建 JavaScript 代码块）以及面向对象的 JavaScript（OOJS）基础（参见对象简介）。
  |
| 目标： | 理解 JavaScript 对象原型、原型链如何工作、如何设置对象的原型。 |

原型链
---

在浏览器控制台中，试着创建一个对象字面量：

```js
const myObject = {
  city: "Madrid",
  greet() {
    console.log(`来自 ${this.city} 的问候`);
  },
};
myObject.greet(); // 来自 Madrid 的问候
```

这里有一个对象，它具有数据属性 `city` 和方法 `greet()`。如果你在控制台中输入对象的名称，然后*跟随一个小数点*（如同 `myObject.`），控制台会列出该对象可用的一系列属性。你会看到，除了 `city` 和 `greet` 外，还有很多其他属性！
```

__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf
```

试着访问其中一个：

```js
myObject.toString(); // "[object Object]"
```

它可以成功调用（即使你不知道 `toString()` 到底在做什么）。

这些额外的属性是什么，它们是从哪里来的？

JavaScript 中所有的对象都有一个内置属性，称为它的 **prototype**（原型）。它本身是一个对象，故原型对象也会有它自己的原型，逐渐构成了**原型链**。原型链终止于拥有 `null` 作为其原型的对象上。

**备注：** 指向对象原型的属性并**不**是 `prototype`。它的名字不是标准的，但实际上所有浏览器都使用 `__proto__`。访问对象原型的标准方法是 `Object.getPrototypeOf()`。

当你试图访问一个对象的属性时：如果在对象本身中找不到该属性，就会在原型中搜索该属性。如果仍然找不到该属性，那么就搜索原型的原型，以此类推，直到找到该属性，或者到达链的末端，在这种情况下，返回 `undefined`。

所以，在调用 `myObject.toString()` 时，浏览器做了这些事情：

* 在 `myObject` 中寻找 `toString` 属性
* `myObject` 中找不到 `toString` 属性，故在 `myObject` 的原型对象中寻找 `toString`
* 其原型对象拥有这个属性，然后调用它。

`myObject` 的原型是什么？为了找到答案，我们可以使用 `Object.getPrototypeOf()` 函数：

```js
Object.getPrototypeOf(myObject); // Object { }
```

有个对象叫 `Object.prototype`，它是最基础的原型，所有对象默认都拥有它。`Object.prototype` 的原型是 `null`，所以它位于原型链的终点：

![myObject 的原型链](/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes/myobject-prototype-chain.svg)

一个对象的原型并不总是 `Object.prototype`，试试这段代码：

```js
const myDate = new Date();
let object = myDate;

do {
  object = Object.getPrototypeOf(object);
  console.log(object);
} while (object);

// Date.prototype
// Object { }
// null
```

这段代码创建了 `Date` 对象，然后遍历了它的原型链，记录并输出了原型。从中我们知道 `myDate` 的原型是 `Date.prototype` 对象，*它*（`Date.prototype`）的原型是 `Object.prototype`。

![myDate 的原型链](/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes/mydate-prototype-chain.svg)

实际上，如果调用了你所熟悉的方法（如 `myDate2.getMonth()`），是在 `Date.prototype` 上定义的方法调用的。

属性遮蔽
----

如果你在一个对象中定义了一个属性，而在该对象的原型中定义了一个同名的属性，会发生什么？我们来看看：

```js
const myDate = new Date(1995, 11, 17);

console.log(myDate.getYear()); // 95

myDate.getYear = function () {
  console.log("别的东西！");
};

myDate.getYear(); // '别的东西！'
```

鉴于对原型链的描述，这应该是可以预测的。当我们调用 `getYear()` 时，浏览器首先在 `myDate` 中寻找具有该名称的属性，如果 `myDate` 没有定义该属性，才检查原型。因此，当我们给 `myDate` 添加 `getYear()` 时，就会调用 `myDate` 中的版本。

这叫做属性的“遮蔽”。

设置原型
----

在 JavaScript 中，有多种设置对象原型的方法，这里我们将介绍两种：`Object.create()` 和构造函数。

### 使用 Object.create

`Object.create()` 方法创建一个新的对象，并允许你指定一个将被用作新对象原型的对象。

这里有个示例：

```js
const personPrototype = {
  greet() {
    console.log("hello!");
  },
};

const carl = Object.create(personPrototype);
carl.greet(); // hello!
```

这里我们创建了一个 `personPrototype` 对象，它有一个 `greet()` 方法。然后我们使用 `Object.create()` 来创建一个以 `personPrototype` 为原型的新对象。现在我们可以在新对象上调用 `greet()`，而原型提供了它的实现。

### 使用构造函数

在 JavaScript 中，所有的函数都有一个名为 `prototype` 的属性。当你调用一个函数作为构造函数时，这个属性被设置为新构造对象的原型（按照惯例，在名为 `__proto__` 的属性中）。

因此，如果我们设置一个构造函数的 `prototype`，我们可以确保所有用该构造函数创建的对象都被赋予该原型：

```js
const personPrototype = {
  greet() {
    console.log(`你好，我的名字是 ${this.name}！`);
  },
};

function Person(name) {
  this.name = name;
}

Object.assign(Person.prototype, personPrototype);
// 或
// Person.prototype.greet = personPrototype.greet;
```

这里我们：

* 创建了一个 `personPrototype` 对象，它具有 `greet()` 方法
* 创建了一个 `Person()` 构造函数，它初始化了要创建人物对象的名字

然后我们使用 Object.assign 将 `personPrototype` 中定义的方法绑定到 `Person` 函数的 `prototype` 属性上。

在这段代码之后，使用 `Person()` 创建的对象将获得 `Person.prototype` 作为其原型，其中自动包含 `greet` 方法。

```js
const reuben = new Person("Reuben");
reuben.greet(); // 你好，我的名字是 Reuben！
```

这也解释了为什么我们之前说 `myDate` 的原型被称为 `Date.prototype`：它是 `Date` 构造函数的 `prototype` 属性。

### 自有属性

我们使用上面的 `Person` 构造函数创建的对象有两个属性：

* `name` 属性，在构造函数中设置，在 `Person` 对象中可以直接看到
* `greet()` 方法，在原型中设置

我们经常看到这种模式，即方法是在原型上定义的，但数据属性是在构造函数中定义的。这是因为方法通常对我们创建的每个对象都是一样的，而我们通常希望每个对象的数据属性都有自己的值（就像这里每个人都有不同的名字）。

直接在对象中定义的属性，如这里的`name`，被称为**自有属性**，你可以使用静态方法 `Object.hasOwn()` 检查一个属性是否是自有属性：

```js
const irma = new Person("Irma");

console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false
```

**备注：** 你也可以在这里使用非静态方法 `Object.hasOwnProperty()`，但我们推荐尽可能使用 `Object.hasOwn()` 方法。

原型与继承
-----

原型是 JavaScript 的一个强大且非常灵活的功能，使得重用代码和组合对象成为可能。

特别是它们支持某种意义的**继承**。继承是面向对象的编程语言的一个特点，它让程序员表达这样的想法：系统中的一些对象是其他对象的更专门的版本。

例如，如果我们正在为一所学校建模，我们可能有*教授*和*学生*：他们都是*人*，所以有一些共同的特征（例如，他们都有名字），但每个人都可能增加额外的特征（例如，教授有一个他们所教的科目），或者可能以不同的方式实现同一个特征。在一个 OOP 系统中，我们可以说教授和学生都**继承自**人。

你可以看到在 JavaScript 中，如果 `Professor` 和 `Student` 对象具有原型 `Person`，那么他们可以继承共同的属性，同时增加和重新定义那些需要不同的属性。

在下一篇文章中，我们将与面向对象编程语言的其他主要特征一起讨论继承，并看看 JavaScript 是如何支持它们的。

总结
--

本文介绍了 JavaScript 对象原型，包括原型对象链如何允许对象相互继承特性，原型属性以及如何使用它来为构造函数添加方法，以及其他相关主题。

在下一篇文章中，我们将了解面向对象编程的基本概念。

* 上一页
* Overview: Objects
* 下一页


为“弹球”示例添加新功能 - 学习 Web 开发

为“弹球”示例添加新功能
============

* 上一页
* Overview: Objects

在此次测验中，你需要将上一节中的“弹球”演示程序作为模板，添加一些新的有趣的功能。

|  |  |
| --- | --- |
| 前提： | 请确保完整学习本章所有内容后再开始测验。 |
| 目标： | 测试你对 JavaScript 对象和面向对象结构的理解。 |

开始
--

请先下载 index.html、style.css 和 main.js 三个文件。

**备注：** 也可以使用 JSBin 或 Glitch 这样的网站来进行测验。你可以选择其中一个将 HTML，CSS 和 JavaScript 粘贴过去。如果你的版本没有单独的 JavaScript / CSS 板块，可以把它们嵌入 HTML 页面内的 `<script>`/`<style>` 元素。

项目简介
----

我们的弹球 demo 很有趣，但是现在我们想让它更具有互动性，我们为它添加一个由玩家控制的“恶魔圈”，如果恶魔圈抓到弹球会把它会吃掉。我们还想测验你面向对象的水平，首先创建一个通用 `Shape()` 对象，然后由它派生出弹球和恶魔圈。最后，我们为 demo 添加一个计分器来记录剩下的球数。

程序最终会像这样：
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>弹球</title>
    <style>
 body {
 margin: 0;
 overflow: hidden;
 font-family: "PingFangSC-Regular", "微软雅黑", sans-serif;
 height: 100%;
 }

 h1 {
 font-size: 2rem;
 letter-spacing: -1px;
 position: absolute;
 margin: 0;
 top: -4px;
 right: 5px;

 color: transparent;
 text-shadow: 0 0 4px white;
 }

 p {
 position: absolute;
 margin: 0;
 top: 35px;
 right: 5px;
 color: #aaa;
 }
 </style>
  </head>

  <body>
    <h1>弹球</h1>
    <p></p>
    <canvas></canvas>

    <script>
 const BALLS\_COUNT = 25;
 const BALL\_SIZE\_MIN = 10;
 const BALL\_SIZE\_MAX = 20;
 const BALL\_SPEED\_MAX = 7;

 class Shape {
 constructor(x, y, velX, velY, exists) {
 this.x = x;
 this.y = y;
 this.velX = velX;
 this.velY = velY;
 this.exists = exists;
 }
 }

 class Ball extends Shape {
 constructor(x, y, velX, velY, color, size, exists) {
 super(x, y, velX, velY, exists);

 this.color = color;
 this.size = size;
 }

 draw() {
 ctx.beginPath();
 ctx.fillStyle = this.color;
 ctx.arc(this.x, this.y, this.size, 0, 2 \* Math.PI);
 ctx.fill();
 }

 update() {
 if (this.x + this.size >= width) {
 this.velX = -this.velX;
 }

 if (this.x - this.size <= 0) {
 this.velX = -this.velX;
 }

 if (this.y + this.size >= height) {
 this.velY = -this.velY;
 }

 if (this.y - this.size <= 0) {
 this.velY = -this.velY;
 }

 this.x += this.velX;
 this.y += this.velY;
 }

 collisionDetect() {
 for (let j = 0; j < balls.length; j++) {
 if (!(this === balls[j])) {
 const dx = this.x - balls[j].x;
 const dy = this.y - balls[j].y;
 const distance = Math.sqrt(dx \* dx + dy \* dy);

 if (distance < this.size + balls[j].size && balls[j].exists) {
 balls[j].color = this.color = randomColor();
 }
 }
 }
 }
 }

 class EvilCircle extends Shape {
 constructor(x, y, exists) {
 super(x, y, exists);

 this.velX = BALL\_SPEED\_MAX;
 this.velY = BALL\_SPEED\_MAX;
 this.color = "white";
 this.size = 10;
 this.setControls();
 }

 draw() {
 ctx.beginPath();
 ctx.strokeStyle = this.color;
 ctx.lineWidth = 3;
 ctx.arc(this.x, this.y, this.size, 0, 2 \* Math.PI);
 ctx.stroke();
 }

 checkBounds() {
 if (this.x + this.size >= width) {
 this.x -= this.size;
 }

 if (this.x - this.size <= 0) {
 this.x += this.size;
 }

 if (this.y + this.size >= height) {
 this.y -= this.size;
 }

 if (this.y - this.size <= 0) {
 this.y += this.size;
 }
 }

 setControls() {
 window.onkeydown = (e) => {
 switch (e.key) {
 case "a":
 case "A":
 case "ArrowLeft":
 this.x -= this.velX;
 break;
 case "d":
 case "D":
 case "ArrowRight":
 this.x += this.velX;
 break;
 case "w":
 case "W":
 case "ArrowUp":
 this.y -= this.velY;
 break;
 case "s":
 case "S":
 case "ArrowDown":
 this.y += this.velY;
 break;
 }
 };
 }

 collisionDetect() {
 for (let j = 0; j < balls.length; j++) {
 if (balls[j].exists) {
 const dx = this.x - balls[j].x;
 const dy = this.y - balls[j].y;
 const distance = Math.sqrt(dx \* dx + dy \* dy);

 if (distance < this.size + balls[j].size) {
 balls[j].exists = false;
 count--;
 para.textContent = "还剩 " + count + " 个球";
 }
 }
 }
 }
 }

 const para = document.querySelector("p");
 const canvas = document.querySelector("canvas");
 const ctx = canvas.getContext("2d");

 const width = (canvas.width = window.innerWidth);
 const height = (canvas.height = window.innerHeight);

 const balls = [];
 let count = 0;

 const evilBall = new EvilCircle(
 random(0, width),
 random(0, height),
 true,
 );

 loop();

 function random(min, max) {
 return Math.floor(Math.random() \* (max - min)) + min;
 }

 function randomColor() {
 return (
 "rgb(" +
 random(0, 255) +
 ", " +
 random(0, 255) +
 ", " +
 random(0, 255) +
 ")"
 );
 }

 function loop() {
 ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
 ctx.fillRect(0, 0, width, height);

 while (balls.length < BALLS\_COUNT) {
 const size = random(BALL\_SIZE\_MIN, BALL\_SIZE\_MAX);
 const ball = new Ball(
 random(0 + size, width - size),
 random(0 + size, height - size),
 random(-BALL\_SPEED\_MAX, BALL\_SPEED\_MAX),
 random(-BALL\_SPEED\_MAX, BALL\_SPEED\_MAX),
 randomColor(),
 size,
 true,
 );
 balls.push(ball);
 count++;
 para.textContent = "还剩 " + count + " 个球";
 }

 for (let i = 0; i < balls.length; i++) {
 if (balls[i].exists) {
 balls[i].draw();
 balls[i].update();
 balls[i].collisionDetect();
 }
 }

 evilBall.draw();
 evilBall.checkBounds();
 evilBall.collisionDetect();

 requestAnimationFrame(loop);
 }
 </script>
  </body>
</html>
```

可以 查看完成版本 来获得更全面的体验。（别偷看源代码哦。）

步骤
--

以下各节介绍你需要完成的步骤。

### 创建我们的新对象

首先，改变你现有的构造器 `Ball()` 使其成为构造器 `Shape()` 并添加一个新的构造器 `Ball()` ：

1. 构造器 `Shape()` 应该像构造器 `Ball()` 那样的方式定义 `x`, `y`, `velX`, 和 `velY` 属性，但不包括 `color` 和 `size` 。
2. 还应该定义一个叫 `exists` 的新属性，用来标记球是否存在于程序中（没有被恶魔圈吃掉）。这应该是一个布尔型（(`true`/`false`）。
3. 构造器 `Ball()` 应该从构造器 `Shape()` 继承 `x`, `y`, `velX`, `velY`,和 `exists` 属性。
4. 构造器 `Ball()` 还应该像最初的构造器 `Ball()` 那样定义一个 `color` 和一个`size` 属性。
5. 请记得给构造器 `Ball()` 的`prototype` 和 `constructor` 属性设置适当的值。

`draw()`, `update()`, 和`collisionDetect()` 方法定义应保持不变。

你还需要为 `new Ball() { ... }` 构造器添加第五个参数——`exists`，且值为 `true`。

到这里，尝试重新加载代码（运行程序），程序以及重新设计的对象都应该像之前那样工作。

### 定义恶魔圈 EvilCircle()

现在是时候来看看那个坏蛋了——恶魔圈 `EvilCircle()`! 我们的游戏中只会有一个恶魔圈，但我们仍然要使用继承自 `Shape()` 的构造器来定义它，这是为让你得到锻炼。之后你可能会想再添加一个由另一个玩家控制的恶魔圈到程序中，或者有几个电脑控制的恶魔圈。你可没法通过一个恶魔圈来掌管程序中的这个世界，但这个评估中就先只这么做吧。

`EvilCircle()` 构造器应该从`Shape()` 继承 `x`, `y`, 和 `exists` ，`velX` 和 `velY` 要恒为 20。

可以这样做：`Shape.call(this, x, y, 20, 20, exists);`

它还应该定义自己的一些属性，如：

* `color`——`'white'`
* `size`——`10`

再次记得给你的 `EvilCircle()` 构造器的传递的参数中定义你继承的属性，并且给`prototype` 和 `constructor` 属性设置适当的值。

### 定义 EvilCircle() 的方法

`EvilCircle()` 应该有以下四个方法：

#### `draw()`

这个方法和 `Ball()`'s `draw()` 方法有着相同的目的：它们把都是对象的实例画在画布上（canvas） 。它们实现的方式差不多，所以你可以先复制 `Ball.prototype.draw` 的定义。然后你需要做下面的修改：

* 我们不想让恶魔圈是实心的，而是一个圈或者说是环。你可以通过将 `fillStyle` 和 `fill()` 修改为 `strokeStyle` 和 `stroke()`而实现这个效果。
* 我们还想让这个圈更厚一点，从而使你能更好地辨认它。可以在调用 `beginPath()` 的后面给 `lineWidth` 赋值实现这个效果。（赋值为 3 就可以了）

#### `checkBounds()`

这个方法和 `Ball()` 的 `update()` 函数做相同的事情——查看恶魔圈是否将要超出屏幕的边界，并且禁止它超出。同样，你可以直接复制 `Ball.prototype.update` 的定义，但是你需要做一些修改：

* 删除最后两行 — 我们不想要在每一帧中自动的更新恶魔圈的位置，因为我们会以下面所述的方式移动它。
* 在 `if()` 语句中，如果检测为真（即小恶魔圈超出边界），我们不需要更新 `velX`/`velY`；取而代之的是，我们想要修改 `x`/`y` 的值，使恶魔圈稍微地弹回屏幕。增加或减去（根据实际判断）恶魔圈 `size` 的值即可实现。

#### `setControls()`

这个方法将会一个 `onkeydown` 的事件监听器给 `window` 对象，这样当特定的键盘按键按下的时候，我们就可以移动恶魔圈。下面的代码块应该放在方法的定义里：

```js
window.onkeydown = (e) => {
  switch (e.key) {
    case "a":
      this.x -= this.velX;
      break;
    case "d":
      this.x += this.velX;
      break;
    case "w":
      this.y -= this.velY;
      break;
    case "s":
      this.y += this.velY;
      break;
  }
};
```

所以当一个按键按下时，事件对象的 key 属性 就可以请求到按下的按键值。如果是代码中那四个指定的键值之一，那么恶魔圈将会左右上下的移动。

**警告：** 英文页面中使用了事件对象的 keyCode 属性，不推荐在新代码中使用该属性，应使用标准 key 属性代替。（详见介绍页面）

**备注：** 这里的 `window.onkeydown` 用一个 箭头函数 代替了英文页面中的匿名函数，从而无需 `var _this = this`。

#### `collisionDetect()`

这个方法和 `Ball()`'s `collisionDetect()` 方法很相似，所以你可以从它那里复制过来作为新方法的基础。但有一些不同之处：

* 在外层的 `if` 语句中，你不需要再检验循环到的小球是否是当前 `collisionDetect()` 所在的对象 — 因为它不再是一个小球了，它是恶魔圈！而是检查小球是否存在（你可以通过哪个属性实现这个呢？）。如果小球不存在，说明它已经被恶魔圈吃掉了，那么就不需要再检测它是否与恶魔圈碰撞了。
* 在里层的 `if` 语句中，你不再需要在碰撞被检测到时去改变对象的颜色 — 而是需要将与恶魔圈发生碰撞的小球设置为不存在（再次提问，你觉得你该怎么实现呢？）。
### 把恶魔圈带到程序中

现在我们已经定义了恶魔圈，我们需要让它显示到我们的屏幕中。为了做这件事，你需要修改一下 `loop()` 函数：

* 首先，创建一个新的恶魔圈的对象实例（指定必需的参数），然后调用它的 `setControls()` 方法。这两件事你只需要做一次，不需要放在 loop 的循环中。
* 在你每一次遍历小球并调用 `draw()`, `update()`, 和 `collisionDetect()` 函数的地方进行修改，使这些函数只会在小球存在时被调用。
* 在每个 loop 的循环中调用恶魔圈实例的方法 `draw()`, `checkBounds()`, 和`collisionDetect()` 。
### 计算得分

为了计算得分，需按照以下步骤：

1. 在你的 HTML 文件中添加一个`<p>` 元素到 `<h1>` (en-US) 元素的下面，其中包含文本 "还剩多少个球"。
2. 在你的 CSS 文件中，添加下面的代码到底部：

```css
p {
  position: absolute;
  margin: 0;
  top: 35px;
  right: 5px;
  color: #aaa;
}
```

3. 在你的 JavaScript 文件中，做下列的修改：
    * 创建一个变量存储段落的引用。
    * 以同样的方式在屏幕上显示小球的数量。
    * 增加球数并在每次将球添加到屏幕里时显示更新的球数量。
    * 减少球数并在每次恶魔吃球时显示更新的球数（因为被吃掉的球不存在了）
提示
--

* 这个评估非常具有挑战性。请仔细按照步骤慢慢来。
* 每完成一个阶段时，你可以保留程序的副本，这是一种有用的方式。这样当你发现你程序出了问题，你可以参考之前的代码。
评定
--

如果你将此评估作为有组织的课程的一部分，你可以将你的成果交给你的老师/导师进行评分。如果你是自学的，通过在 Learning Area Discourse thread，或者在 Mozilla IRC 的 #mdn IRC 频道上申请，你可以十分容易地得到评分指南。首先先尝试这个练习，作弊不会有任何收获。

* 上一页
* Overview: Objects


使用 JSON - 学习 Web 开发

使用 JSON
=======

* 上一页
* Overview: Objects
* 下一页

JavaScript 对象表示法（JSON）是用于将结构化数据表示为 JavaScript 对象的标准格式，通常用于在网站上表示和传输数据（例如从服务器向客户端发送一些数据，因此可以将其显示在网页上）。你会经常遇到它，所以在本文中，我们向你提供使用 JavaScript 处理 JSON 的所有工作，包括访问 JSON 对象中的数据项并编写自己的 JSON。

|  |  |
| --- | --- |
| 前提： |
 具备基本的计算机知识、对 HTML 和 CSS 的基本了解、熟悉 JavaScript
 基础知识（阅读 JavaScript 第一步和创建代码块）和 JS
 面向对象基础（阅读对象介绍 (en-US)）。
  |
| 目标： | 理解 JSON 的数据储存工作原理，创建你的 JSON 对象。 |

什么是 JSON？
---------

JSON 是一种按照 JavaScript 对象语法的数据格式，这是道格拉斯·克罗克福特推广的。虽然它是基于 JavaScript 语法，但它独立于 JavaScript，这也是为什么许多程序环境能够读取（解读）和生成 JSON。

JSON 可以作为一个对象或者字符串存在，前者用于解读 JSON 中的数据，后者用于通过网络传输 JSON 数据。这不是一个大事件——JavaScript 提供一个全局的 可访问的 JSON 对象来对这两种数据进行转换。

**备注：** 将字符串转换为原生对象称为*反序列化*（deserialization），而将原生对象转换为可以通过网络传输的字符串称为*序列化*（serialization）。

一个 JSON 对象可以被储存在它自己的文件中，这基本上就是一个文本文件，扩展名为 `.json`，还有 `application/json` MIME 类型。

### JSON 结构

如上所述，JSON 是一个字符串，其格式非常类似于 JavaScript 对象字面量的格式。你可以在 JSON 中包含与标准 JavaScript 对象相同的基本数据类型——字符串、数字、数组、布尔值和其他对象字面量。这使你可以构建一个数据层次结构，如下所示：

```json
{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
}
```

如果我们把字符串加载到 JavaScript 程序中，并将其解析到一个名为 `superHeroes` 的变量，那么我们就可以使用在 JavaScript 对象基础文章中相同的点/括号表示法来访问其中的数据。例如：

```js
superHeroes.hometown;
superHeroes["active"];
```

为了访问层次结构中更深层次的数据，必须将所需的属性名和数组索引链接在一起。例如，访问 members 数组第二个英雄的第三个超能力，可以这样做：

```js
superHeroes["members"][1]["powers"][2];
```

1. 首先我们有变量名 `superHeroes`，储存对象。
2. 在对象中我们想访问 `members` 属性，所以我们使用 `["members"]`。
3. `members` 包含有对象数组，我们想要访问第二个元素，所以我们使用 `[1]`。
4. 在对象内，我们想访问 `powers` 属性，所以我们使用 `["powers"]`。
5. `powers` 属性是一个包含英雄技能的数组。我们想要第三个，所以我们使用 `[2]`。

**备注：** 我们已经在 JSONText.html 实例中让 JSON 对象进入变量中使其可访问（见源代码）。尝试加载它并且在你的浏览器上访问对象数据。

### JSON 数组

前面我们提到，JSON 文本基本上看起来像字符串中的 JavaScript 对象。我们也可以将数组与 JSON 相互转换。下面也是有效的 JSON，例如：

```json
[
  {
    "name": "Molecule Man",
    "age": 29,
    "secretIdentity": "Dan Jukes",
    "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
  },
  {
    "name": "Madame Uppercut",
    "age": 39,
    "secretIdentity": "Jane Wilson",
    "powers": [
      "Million tonne punch",
      "Damage resistance",
      "Superhuman reflexes"
    ]
  }
]
```

上面是完全合法的 JSON。你只需要通过数组索引就可以访问数组元素，如 `[0]["powers"][0]`。

### 其他注意事项

* JSON 是一种纯数据格式，它只包含属性，没有方法。
* JSON 要求在字符串和属性名称周围使用双引号。单引号无效。
* 甚至一个错位的逗号或分号就可以导致 JSON 文件出错。你应该小心的检查你想使用的数据（虽然计算机生成的 JSON 很少出错，只要生成程序正常工作）。你可以通过像 JSONLint 这样的应用程序来验证 JSON。
* JSON 实际上可以是任何可以有效包含在 JSON 中的数据类型的形式。比如，单个字符串或者数字就是有效的 JSON 对象。
* 与 JavaScript 代码中对象属性可以不加引号不同，JSON 中只有带引号的字符串可以用作属性。
动手练习：一个 JSON 示例
---------------

好了，让我们通过运行这个示例来展示我们如何利用 JSON 数据。

### 开始吧

首先，拷贝我们的 heroes.html 和 style.css 文件。后者包含了用于页面的简单的 CSS，前者包含了简单的 HTML body，以及一个 `<script>` 元素，其中包含我们将在练习中编写的 JavaScript 代码：

```html
<header>
...
</header>

<section>
...
</section>

<script>
...
</script>
```

我们已经把 JSON 数据放在了 GitHub 上面：https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json

我们准备把它加载到我们的页面中，然后使用漂亮的 DOM 操作来展示它，就像这样：

![](/zh-CN/docs/Learn/JavaScript/Objects/JSON/json-superheroes.png)

### 顶层函数

顶层函数（top-level function）的代码如下所示：

```js
async function populate() {
  const requestURL =
    "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
  const request = new Request(requestURL);

  const response = await fetch(request);
  const superHeroes = await response.json();

  populateHeader(superHeroes);
  populateHeroes(superHeroes);
}
```

 为了获取 JSON 数据，我们使用了名为 Fetch 的 API。
 该 API 允许我们通过 JavaScript 进行网络请求，从服务器检索资源（如图像、文本、JSON，甚至 HTML 片段），这意味着我们可以仅更新页面的小部分内容而无需重新加载整个页面。

在我们的函数中，前四行使用 Fetch API 从服务器获取 JSON 数据：

* 我们声明了 `requestURL` 变量以存储 GitHub 的 URL
* 我们使用该 URL 初始化一个新的 `Request` 对象。
* 我们使用 `fetch()` 函数进行网络请求，它返回一个 `Response` 对象
* 我们使用 `Response` 对象的 `json()` 函数将响应作为 JSON 获取。

**备注：**`fetch()` API 是**异步**的。我们将在下一个模块中详细了解有关异步函数的知识，但现在我们只需知道需要在使用 fetch API 的函数名称之前添加 `async` 关键字，并在任何异步函数的调用之前添加 `await` 关键字。

在这一切之后，`superHeroes` 变量将包含基于 JSON 的 JavaScript 对象。然后，我们将该对象传递给两个函数调用——第一个函数用正确的数据填充 `<header>`，而第二个函数为团队中的每个英雄创建一个信息卡，并将其插入到 `<section>` 中。

### 填充 header

现在我们已经获得我们的 JSON 数据，让我们利用它来写两个我们使用的函数。首先，添加下面的代码于之前的代码下方：

```js
function populateHeader(obj) {
  const header = document.querySelector("header");
  const myH1 = document.createElement("h1");
  myH1.textContent = obj.squadName;
  header.appendChild(myH1);

  const myPara = document.createElement("p");
  myPara.textContent = `Hometown: ${obj.homeTown} // Formed: ${obj.formed}`;
  header.appendChild(myPara);
}
```

这里我们首先使用 `createElement()` 创建一个 h1 元素，然后将其 `textContent` 设置为对象的 `squadName` 属性，接着使用 `appendChild()` 将其添加到页眉中。然后，我们使用类似的操作来创建一个段落：创建并设置其文本内容，再将其附加到页眉。唯一的区别在于，它的文本设置为一个包含对象的 `homeTown` 和 `formed` 属性的模板字面量。

### 创建英雄信息卡片

接下来，添加如下的函数到代码底部，这个函数创建和展示了超级英雄的卡片：

```js
function populateHeroes(obj) {
  const section = document.querySelector("section");
  const heroes = obj.members;

  for (const hero of heroes) {
    const myArticle = document.createElement("article");
    const myH2 = document.createElement("h2");
    const myPara1 = document.createElement("p");
    const myPara2 = document.createElement("p");
    const myPara3 = document.createElement("p");
    const myList = document.createElement("ul");

    myH2.textContent = hero.name;
    myPara1.textContent = `Secret identity: ${hero.secretIdentity}`;
    myPara2.textContent = `Age: ${hero.age}`;
    myPara3.textContent = "Superpowers:";

    const superPowers = hero.powers;
    for (const power of superPowers) {
      const listItem = document.createElement("li");
      listItem.textContent = power;
      myList.appendChild(listItem);
    }

    myArticle.appendChild(myH2);
    myArticle.appendChild(myPara1);
    myArticle.appendChild(myPara2);
    myArticle.appendChild(myPara3);
    myArticle.appendChild(myList);

    section.appendChild(myArticle);
  }
}
```

首先，我们保存了 JSON 的 `members` 属性作为一个变量。这个数组含有多个带有英雄信息的对象。

接下来，我们使用一个循环来，遍历每个元素。对于每一个元素，我们：

1. 创建几个元素：一个 `<article>`、一个 `<h2>`、三个 `<p>` 和一个 `<ul>`。
2. 设置 `<h2>` 为当前英雄的 `name`。
3. 使用他们的 `secretIdentity`、`age`，以及“Superpowers:”介绍信息列表来填充三个段落。
4. 保存 `powers` 属性于另一个变量 `superPowers`——包含英雄的超能力的列表。
5. 使用另一个循环来遍历当前的英雄的超能力，对于每一个元素我们创建 `<li>` 元素，并放入超能力，然后使用 `appendChild()` 把 `listItem` 放入`<ul>` 元素（`myList`）中。
6. 最后一件事情是将 `<h2>`、`<p>` 和 `<ul>` 追加到 `<article>`（`myArticle`）中。然后将 `<article>` 追加到 `<section>`。追加的顺序很重要，因为它们将被展示在 HTML 中。

**备注：** 如有疑难，试试引用我们的 heroes-finished.html 代码（也可以查看运行实例）。

**备注：** 如果你对访问 JSON 对象的点/括号表示法有困扰。获得文件 superheroes.json，在你的编辑器中打开并参考我们的 JS 代码将会有所帮助。你还应该参考我们的 JavaScript 对象基础文章，了解关于点和括号表示法的更多信息。

### 调用顶层函数

最后，我们需要调用顶层函数 `populate()`：

```js
populate();
```

对象和文本间的转换
---------

上面的示例在访问 JavaScript 对象方面很简单，因为我们直接使用 `response.json()` 将网络响应转换为了 JavaScript 对象。

但是有时候我们没有那么幸运，我们接收到一些 字符串作为 JSON 数据，然后我们想要将它转换为对象。当我们想要发送 JSON 数据作为信息，我们将需要转换它为字符串，我们经常需要正确的转换数据，幸运的是，这两个问题在 web 环境中是那么普遍以至于浏览器拥有一个内建的 JSON，包含以下两个方法。

* `parse()`：以文本字符串形式接受 JSON 对象作为参数，并返回相应的对象。
* `stringify()`：接收一个对象作为参数，返回一个对应的 JSON 字符串。

你可以看看我们 heroes-finished-json-parse.html 示例的第一个操作（查看源代码），这做了一件与我们之前一模一样的事情，除了：

* 我们通过调用 `text()` 方法将响应作为文本获取，而不是 JSON
* 然后我们使用 `parse()` 将文本转换为 JavaScript 对象。

关键片段如下：

```js
async function populate() {
  const requestURL =
    "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
  const request = new Request(requestURL);

  const response = await fetch(request);
  const superHeroesText = await response.text();

  const superHeroes = JSON.parse(superHeroesText);
  populateHeader(superHeroes);
  populateHeroes(superHeroes);
}
```

正如你所想，`stringify()` 做相反的事情。尝试将下面的代码输入你的浏览器 JS 控制台来看看会发生什么：

```js
let myObj = { name: "Chris", age: 38 };
myObj;
let myString = JSON.stringify(myObj);
myString;
```

这儿我们创建了一个 JavaScript 对象，接着检查了它包含了什么，然后用 `stringify()` 将它转换成 JSON 字符串，最后保存返回值作为变量并再一次检查。

技能测试！
-----

你已经到达本文的末尾，但你是否记住了最重要的信息呢？在继续之前，你可以进行一些进一步的测试，以验证你是否记住了这些信息——请参阅技能测试：JSON (en-US)。

总结
--

在这个文章中，我们给了你一个简单的示例来在自己的程序中使用 JSON，包括创建和处理 JSON，还有如何访问 JSON 内的数据。在下一篇文章中我们将开始关注 JS 中的面向对象内容。

参见
--

* JSON 对象
* Fetch API
* 使用 Fetch
* HTTP 请求方法
* JSON 官网（含 ECMA 标准链接）
* 上一页
* Overview: Objects
* 下一页