## 十三、浏览器中的 JavaScript

> 原文：[JavaScript and the Browser](https://eloquentjavascript.net/13_browser.html)
> 
> 译者：[飞龙](https://github.com/wizardforcel)
> 
> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
> 
> 部分参考了[《JavaScript 编程精解（第 2 版）》](https://book.douban.com/subject/26707144/)

> Web 背后的梦想是公共信息空间，其中我们通过共享信息进行交流。 其普遍性至关重要：超文本链接可指向任何东西，无论是个人的，本地的还是全球的，无论是草稿还是高度润色的。
> 
> Douglas Crockford，《JavaScript 编程语言》（视频讲座）

![](img/13-0.jpg)

本书接下来的章节将会介绍 Web 浏览器。可以说，没有浏览器，就没有 JavaScript。就算有，估计也不会有多少人去关心这门编程语言。

Web 技术自出现伊始，其演变方式和技术上就是以分散的方式发展的。许多浏览器厂商专门为其开发新的功能，有时这些新功能被大众采纳，有时这些功能被其他功能所代替，最终形成了一套标准。

这种发展模式是把双刃剑。一方面，不会有一个集中式的组织来管理技术的演进，取而代之的是一个包含多方利益集团的松散协作架构（偶尔会出现对立）。另一方面，互联网这种无计划的发展方式所开发出来的系统，其内部很难实现一致性。事实上，它的一些部分令人疑惑，并且毫无设计。

## 网络和 Internet

计算机网络出现在 20 世纪 50 年代。如果在两台或多台计算机之间铺设电缆，那么你可以通过这些电缆互相收发数据，并实现一些神奇的功能。

如果通过连接同一个建筑中的两台机器就可以实现一些神奇的功能，那么如果可以连接全世界的机器，就可以完成更伟大的工作了。20 世纪 80 年代，人们开发了相关技术来实现这个愿景，我们将其产生的网络称为 Internet。而 Internet 的表现名副其实。

计算机可以使用这种网络向其他计算机发送位数据。为了在传输位数据的基础上，实现计算机之间的有效通信，网络两端的机器必须知道这些位所表达的实际含义。对于给定的位序列，其含义完全取决于位序列描述的信息类型与使用的编码机制。

网络协议描述了一种网络通信方式。网络协议非常多，其中包括邮件发送、邮件收取和邮件共享，甚至连病毒软件感染控制计算机都有相应的协议。

例如，HTTP（超文本传输协议，Hypertext Transfer Protocol）是用于检索命名资源（信息块，如网页或图片）的协议。 它指定发出请求的一方应该以这样的一行开始，命名资源和它正在尝试使用的协议的版本。

```
GET /index.html HTTP/1.1
```

有很多规则，关于请求者在请求中包含更多信息的方式，以及另一方返回资源并打包其内容的方式。 我们将在第 18 章中更详细地观察 HTTP。

大多数协议都建立在其他协议之上。 HTTP 将网络视为一种流式设备，您可以将位放入这些设备，并使其按正确的顺序到达正确的目的地。 我们在第 11 章]中看到，确保这些事情已经是一个相当困难的问题。


TCP（传输控制协议，Transmission Control Protocol）就可以帮助我们解决该问题。所有连接到互联网的设备都会使用到这种协议，而多数互联网通信都构建在这种协议之上。

TCP 连接的工作方式是一台电脑必须等待或者监听，而另一台电脑则开始与之通信。一台机器为了同时监听不同类型的通信信息，会为每个监听器分配一个与之关联的数字（我们称之为端口）。大多数协议都指定了默认使用的端口。例如，当我们向使用 SMTP 协议发送一封邮件时，我们需要通过一台机器来发送邮件，而发送邮件的机器需要监听端口 25。

随后另一台机器连接到使用了正确端口号的目标机器上。如果可以连接到目标机器，而且目标机器在监听对应端口，则说明连接创建成功。负责监听的计算机名为服务器，而连接服务器的计算机名为客户端。

我们可以将该连接看成双向管道，位可以在其中流动，也就是说两端的机器都可以向连接中写入数据。当成功传输完这些位数据后，双方都可以读取另一端传来的数据。TCP 是一个非常便利的模型。我们可以说TCP就是一种网络的抽象。

## Web

万维网（World Wide Web，不要将其与 Internet 混淆）是包含一系列协议和格式的集合，允许我们通过浏览器访问网页。词组中的 Web 指的是这些页面可以轻松地链接其他网页，因此最后可以连接成一张巨大的网，用户可以在网络中浏览。

你只需将一台计算机连接到 Internet 并使用 HTTP 监听 80 端口，就可以成为 Web 的一部分。其他计算机可以通过网络，并使用 HTTP 协议获取其他计算机上的文件。

网络中的每个文件都能通过 URL（统一资源定位符，Universal Resource Locator）访问，如下所示：

```
  http://eloquentjavascript.net/13_browser.html
 |      |                      |               |
 protocol       server               path
```
该地址的第一部分告诉我们 URL 使用的是 HTTP 协议（加密的 HTTP 连接则使用`https://`来表示）。第二部分指的是获取文件的服务器地址。第三部分是我们想要获取的具体文件（或资源）的路径。

连接到互联网的机器获得一个 IP 地址，该地址是一个数字，可用于将消息发送到该机器的，类似于`"149.210.142.219"`或`"2001:4860:4860::8888"`。 但是或多或少的随机数字列表很难记住，而且输入起来很笨拙，所以你可以为一个特定的地址或一组地址注册一个域名。 我注册了`eloquentjavascript.net`，来指向我控制的机器的 IP 地址，因此可以使用该域名来提供网页。

如果你在浏览器地址栏中输入上面提到的 URL，浏览器会尝试获取并显示该 URL 对应的文档。首先，你的浏览器需要找出域名`eloquentjavascript.net`指向的地址。然后使用 HTTP 协议，连接到该地址处的服务器，并请求`/13_browser.html`这个资源。如果一切顺利，服务器会发回一个文档，然后您的浏览器将显示在屏幕上。

## HTML

HTML，即超文本标记语言（Hypertext Markup Language），是在网页中得到广泛使用的文档格式。HTML 文档不仅包含文本，还包含了标签，用于说明文本结构，描述了诸如链接、段落、标题之类的元素。

一个简短的 HTML 文档如下所示：

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My home page</title>
  </head>
  <body>
    <h1>My home page</h1>
    <p>Hello, I am Marijn and this is my home page.</p>
    <p>I also wrote a book! Read it
      <a href="http://eloquentjavascript.net">here</a>.</p>
  </body>
</html>
```

标签包裹在尖括号之间（`<`和`>`，小于和大于号），提供关于文档结构的信息。其他文本则是纯文本。

文档以`<!doctype html>`开头，告诉浏览器将页面解释为现代 HTML，以别于过去使用的各种方言。

HTML 文档有头部（head）和主体（body）。头部包含了文档信息，而主体则包含文档自身。在本例中，头部将文档标题声明为`"My home page"`，并使用 UTF-8 编码，它是将 Unicode  文本编码为二进制的方式。文档的主体包含标题（`<h1>`，表示一级标题，`<h2>`到`<h6>`可以产生不同等级的子标题）和两个段落（`<p>`）。

标签有几种形式。一个元素，比如主体、段落或链接以一个起始标签（比如`<p>`）开始，并以一个闭合标签（比如`</p>`）结束。一些起始标签，比如一个链接（`<a>`），会包含一些额外信息，其形式是`name="value"`这种键值对，我们称之为属性。在本例中，使用属性`href="http://eloquentjavascript.net"`指定链接的目标，其中`href`表示“超文本链接（Hypertext Reference）”。

某些类型的标签不会包含任何元素，这种标签不需要闭合。元数据标签`<meta charset="utf-8">`就是一个例子。

> 译者注：最好还是这样闭合它们：`<meta charset="utf-8" />`。

尽管 HTML 中尖括号有特殊含义，但为了在文档的文本中包含这些符号，可以引入另外一种形式的特殊标记方法。普通文本中的起始尖括号写成`&lt;`（less than），而闭合尖括号写成`&gt;`（greater than）。在 HTML 中，我们将一个`&`字符后跟着一个单词和分号（`;`）这种写法称为一个实体，浏览器会使用实体编码对应的字符替换它们。

与之类似的是 JavaScript 字符串中反斜杠的使用。由于 HTML 中的实体机制赋予了`&`特殊含义，因此我们需要使用`&amp;`来表示一个`&`字符。在属性的值（包在双引号中）中使用`&quot;`可以插入实际的引号字符。

HTML 的解析过程容错性非常强。当应有的标签丢失时，浏览器会重新构建这些标签。标签的重新构建已经标准化，你可以认为所有现代浏览器的行为都是一致的。

下面的文件与之前版本显示效果相同：

```html
<!doctype html>

<meta charset=utf-8>
<title>My home page</title>

<h1>My home page</h1>
<p>Hello, I am Marijn and this is my home page.
<p>I also wrote a book! Read it
  <a href=http://eloquentjavascript.net>here</a>.
```

`<html>`、`<head>`和`<body>`标签可以完全丢弃。浏览器知道`<meta>`和`<title>`属于头部，而`<h1>`属于主体。此外，我再也不用明确关闭某个段落，因为新段落开始或文档结束时，浏览器会隐式关闭段落标签。目标链接两边的引号也可以丢弃。

本书的示例通常都会省略`<html>`、`<head>`和`<body>`标签，以保持源代码简短，避免太过杂乱。但我会明确关闭所有标签并在属性两旁包含引号。

本书也会经常忽略`doctype`和`charset`声明。这并不是鼓励大家省略它们。当你忘记它们时，浏览器往往会做出荒谬的事情。 您应该认为`doctype`和`charset`元数据隐式出现在示例中，即使它们没有实际显示在文本中。

## HTML 和 JavaScript

对于本书来说，最重要的一个 HTML 标签是`<script>`。该标签允许我们在文档中包含一段 JavaScript 代码。

```html
<h1>Testing alert</h1>
<script>alert("hello!");</script>
```

当浏览器在读取 HTML 时，一旦遇到`<script>`标签就会执行该代码。这个页面在打开时会弹出一个对话框 - `alert`函数类似`prompt`，因为它弹出一个小窗口，但只显示一条消息而不请求输入。

在 HTML 文档中包含大程序是不切实际的。`<script>`标签可以指定一个`src`属性，从一个 URL 获取脚本文件（包含 JavaScript 程序的文本文件）。

```html
<h1>Testing alert</h1>
<script src="code/hello.js"></script>
```

这里包含的文件`code/hello.js`是和上文中相同的一段程序，`alert("hello")`。当一个页面将其他 URL 引用为自身的一部分时（比如图像文件或脚本），网页浏览器将会立即获取这些资源并将其包含在页面中。

即使`script`标签引用了一个文本文件，且并未包含任何代码，你也必须使用`</script>`来闭合标签。如果你忘记了这点，浏览器会将剩余的页面会作为脚本的一部分进行解析。

你可以在浏览器中加载ES模块（参见第 10 章），向脚本标签提供`type ="module"`属性。 这些模块可以依赖于其他模块，通过将相对于自己的 URL 用作`import`声明中的模块名称。


某些属性也可以包含 JavaScript 程序。下面展示的`<button>`标签（显示一个按钮）有一个`onclick`属性。该属性的值将在点击按钮时运行。

```html
<button onclick="alert('Boom!');">DO NOT PRESS</button>
```

需要注意的是，我们在`onclick`属性的字符串中使用了单引号，这是因为我们在使用了双引号来引用整个属性。我们也可以使用`&quot;`。

## 沙箱

直接执行从因特网中下载的程序存在潜在危险。你不了解大多数的网页开发者，他们不一定都心怀善意。一旦运行某些不怀好意的人提供的程序，你的电脑可能会感染病毒，这些程序还会窃取数据会并盗走账号。

但网络的吸引力就在于你可以浏览网站，而不必要信任所有网站。这就是为什么浏览器严重限制了 JavaScript 程序的能力—— JavaScript 无法查看电脑中的任何文件，也无法修改与其所在页面无关的数据。

我们将这种隔离程序运行环境的技术称为沙箱。以该思想编写的程序在沙箱中运行，不会对计算机造成任何伤害。但是你应该想象，这种特殊的沙箱上面有一个厚钢筋笼子，所以在其中运行的程序实际上不会出去。

实现沙箱的难点是：一方面我们要给予程序一定的自由使得程序能有实际用处，但又要限制程序，防止其执行危险的行为。许多实用功能（比如与服务器通信或从剪贴板读取内容）也会存在问题，有些侵入者可以利用这些功能来侵入你的计算机。

时不时会有一些人想到新方法，突破浏览器的限制，并对你的机器造成伤害，从窃取少量的私人信息到掌握执行浏览器的整个机器。浏览器开发者的对策是修补漏洞，然后一切都恢复正常。直到下一次问题被发现并广为传播之前，某些政府或秘密组织可以私下利用这些漏洞。

## 兼容性与浏览器之争

在 Web 技术发展的早期，一款名为 Mosaic 的浏览器统治了整个市场。几年之后，这种平衡被 Netscape 公司打破，随后又被微软的 Internet Explorer 排挤出市场。无论什么时候，当一款浏览器统治了整个市场，浏览器供应商就会觉得他们有权利单方面为网络研发新的特性。由于大多数人都使用相同的浏览器，因此网站会开始使用这些独有特性，也就不再考虑其他浏览器的兼容性问题了。

这是兼容性的黑暗时代，我们通常称之为浏览器之争。网络开发者总是为缺乏统一的 Web 标准，而需要去考虑两到三种互不兼容的平台而感到烦恼。让事情变得更糟糕的是 2003 年左右使用的浏览器充满了漏洞，当然不同浏览器的漏洞都不一样。网页编写者的生活颇为艰辛。

Mozilla Firefox，作为 Netscape 浏览器的非盈利性分支，在20世纪初末期开始挑战 Internet Explorer 的霸主地位。因为当时微软并未特别关心与其竞争，导致 Firefox 迅速占领了很大的市场份额。与此同时，Google 发布了它的 Chrome 浏览器，而 Apple 的 Safari 也得到普及，导致现在成为四个主要选手的竞争，而非一家独大。

新的参与者对标准有着更认真的态度，和更好的工程实践，为我们减少了不兼容性和错误。 微软看到其市场份额极速下降，在其 Edge 浏览器中采取了这些态度，取代了 Internet Explorer。 如果您今天开始学习网络开发，请认为自己是幸运的。 主流浏览器的最新版本行为非常一致，并且错误相对较少。

这并不是说就没有问题了。某些使用网络的人，出于惰性或公司政策，被迫使用旧版本的浏览器。直到这些浏览器完全退出市场之前，为旧版本浏览器编写网站仍需要掌握很多不常见的特性，了解旧浏览器的缺陷和特殊之处。本书不会讨论这些特殊的特性，而着眼于介绍现代且健全的网络程序设计风格。
