JavaScript 和浏览器

网络背后的梦想是创造一个共同的信息空间,我们通过共享信息进行交流。它的普遍性至关重要:一个超文本链接可以指向任何东西,无论是个人、本地还是全球,无论是草稿还是高度抛光。

蒂姆·伯纳斯·李,《万维网:一个非常简短的个人历史》
Illustration showing a telephone switchboard

本书的接下来的几章将讨论网络浏览器。如果没有浏览器,就不会有 JavaScript——或者如果有,也不会有人关注它。

网络技术从一开始就是去中心化的,不仅在技术上,而且在它的发展方式上。各种浏览器供应商以临时的方式,有时是经过不周全的考虑,添加了新的功能,然后——有时——被其他人采用,最后被写进标准。

这既是福也是祸。一方面,在没有一个中心化的机构控制系统的情况下,而是由各种各样的参与者以松散的合作(或者偶尔,公开的敌意)来改进它,这是很令人振奋的。另一方面,网络开发的随意方式意味着最终产生的系统并非内部一致性的典范。其中某些部分简直让人困惑,设计糟糕。

网络和互联网

计算机网络自 20 世纪 50 年代就已经存在。如果您在两台或多台计算机之间架设电缆,并允许它们通过这些电缆来回发送数据,那么您就可以做各种奇妙的事情。

如果连接同一建筑物中的两台机器可以让我们做奇妙的事情,那么连接遍布全球的机器应该会更好。实现这一愿景的技术是在 20 世纪 80 年代开发的,由此产生的网络被称为互联网。它已经兑现了它的承诺。

一台计算机可以使用这个网络向另一台计算机发送比特。为了让这种比特射击产生任何有效的通信,两端的计算机都必须知道这些比特应该代表什么。任何给定比特序列的含义完全取决于它试图表达的类型以及使用的编码机制。

网络协议描述了在网络上进行通信的方式。有用于发送电子邮件的协议、用于获取电子邮件的协议、用于共享文件的协议,甚至用于控制碰巧感染了恶意软件的计算机的协议。

超文本传输协议 (HTTP) 是一种用于检索命名资源(信息块,例如网页或图片)的协议。它规定,发出请求的一方应以类似这样的行开头,其中包含资源的名称以及它试图使用的协议版本

GET /index.html HTTP/1.1

关于请求者如何将更多信息包含在请求中以及返回资源的另一方如何打包其内容的方式,还有许多其他规则。我们将在第 18 章中更详细地介绍 HTTP。

大多数协议都是建立在其他协议之上的。HTTP 将网络视为一个类似流的设备,您可以将比特放入其中,并让它们按正确顺序到达正确的位置。在网络提供的原始数据发送的基础上提供这些保证本身就是一个相当棘手的问题。

传输控制协议 (TCP) 是一个解决此问题的协议。所有连接到互联网的设备都“使用”它,互联网上的大多数通信都是建立在它之上的。

TCP 连接的工作方式如下:一台计算机必须等待,或监听,其他计算机开始与它通信。为了能够在同一台机器上同时监听不同类型的通信,每个监听器都有一个与其关联的数字(称为端口)。大多数协议指定默认情况下应使用哪个端口。例如,当我们想使用 SMTP 协议发送电子邮件时,我们发送它的机器预计会在端口 25 上监听。

然后,另一台计算机可以通过使用正确的端口号连接到目标机器来建立连接。如果目标机器可以到达并且正在该端口上监听,则连接将成功创建。监听的计算机被称为服务器,连接的计算机被称为客户端

这样的连接充当一个双向管道,比特可以通过它流动——两端的机器都可以将数据放入其中。一旦比特成功传输,它们就可以被另一端的机器再次读出。这是一个方便的模型。你可以说 TCP 提供了网络的抽象。

万维网

万维网(不要与整个互联网混淆)是一组协议和格式,允许我们在浏览器中访问网页。Web这个词指的是这些页面可以轻松地相互链接,从而连接成一个巨大的网格,用户可以在其中移动。

要成为网络的一部分,您需要做的就是将一台机器连接到互联网,并让它在端口 80 上使用 HTTP 协议监听,以便其他计算机可以向它请求文档。

网络上的每个文档都由一个统一资源定位器 (URL) 命名,它看起来像这样

  https://eloquent.javascript.ac.cn/13_browser.html
 |      |                      |               |
 protocol       server               path

第一部分告诉我们此 URL 使用 HTTP 协议(与例如加密的 HTTP 相反,它将是https://)。然后是识别我们从哪个服务器请求文档的部分。最后是一个路径字符串,用于识别我们感兴趣的文档(或资源)。

连接到互联网的机器会获得一个IP 地址,一个可用于向该机器发送消息的数字,它看起来像149.210.142.2192001:4860:4860::8888。由于或多或少随机数字的列表很难记忆且难以输入,因此您可以为地址或地址集注册一个域名。我注册了eloquentjavascript.net 来指向我控制的机器的 IP 地址,因此可以使用该域名来提供网页。

如果您在浏览器的地址栏中键入此 URL,浏览器将尝试检索并显示该 URL 处的文档。首先,您的浏览器必须找出eloquentjavascript.net 指向哪个地址。然后,使用 HTTP 协议,它将连接到该地址上的服务器并请求/13_browser.html 资源。如果一切顺利,服务器将返回一个文档,您的浏览器随后会将其显示在您的屏幕上。

HTML

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="https://eloquent.javascript.ac.cn">here</a>.</p>
  </body>
</html>

标签用尖括号(<>小于大于符号)括起来,提供有关文档结构的信息。其他文本只是纯文本。

文档以<!doctype html> 开头,它告诉浏览器将页面解释为现代 HTML,而不是过去使用的过时样式。

HTML 文档有一个头部和一个主体。头部包含有关文档的信息,主体包含文档本身。在本例中,头部声明此文档的标题是“我的主页”,并且它使用 UTF-8 编码,这是一种将 Unicode 文本编码为二进制数据的方式。文档的主体包含一个标题(<h1>,意思是“标题 1”——<h2><h6> 生成子标题)和两个段落(<p>)。

标签有多种形式。元素,例如主体、段落或链接,由类似<p>开始标签启动,并由类似</p>结束标签结束。某些开始标签,例如链接的标签(<a>),在属性形式的name="value" 对中包含额外信息。在本例中,链接的目标用href="http://eloquentjavascript.net" 表示,其中href 代表“超文本引用”。

某些类型的标签不会包含任何内容,因此不需要关闭。元数据标签<meta charset="utf-8"> 就是这种情况的一个例子。

为了能够在文档的文本中包含尖括号,即使它们在 HTML 中具有特殊含义,也必须引入另一种特殊符号。一个普通的开始尖括号写成&lt;(“小于”),一个结束尖括号写成&gt;(“大于”)。在 HTML 中,一个与号 (&) 字符后跟一个名称或字符代码,最后是一个分号 (;) 称为实体,将被它编码的字符替换。

这类似于在 JavaScript 字符串中使用反斜杠的方式。由于这种机制也赋予了与号字符特殊的含义,因此需要将其转义为&amp;。在用双引号括起来的属性值内部,可以使用&quot; 插入一个文字引号字符。

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=https://eloquent.javascript.ac.cn>here</a>.

<html><head><body> 标签已完全消失。浏览器知道 <meta><title> 属于头部,而 <h1> 表示主体已开始。此外,我不再显式地关闭段落,因为打开一个新段落或结束文档将隐式地关闭它们。属性值周围的引号也不见了。

本书通常会从示例中省略 <html><head><body> 标签,以保持示例简洁且无杂乱。但我关闭标签,并在属性周围包含引号。

我通常还会省略 doctype 和 charset 声明。不要将此视为鼓励您从 HTML 文档中删除它们。当您忘记它们时,浏览器通常会执行荒谬的操作。将 doctype 和 charset 元数据视为隐式存在于示例中,即使它们实际上没有在文本中显示。

HTML 和 JavaScript

在这本书的上下文中,最重要的 HTML 标签是 <script>,它允许我们在文档中包含一段 JavaScript 代码。

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

这样的脚本将在浏览器读取 HTML 时遇到 <script> 标签时立即运行。此页面在打开时会弹出一个对话框——alert 函数类似于 prompt,因为它会弹出一个小型窗口,但只显示一条消息而不请求输入。

直接在 HTML 文档中包含大型程序通常不切实际。可以为 <script> 标签提供一个 src 属性,以便从 URL 获取一个脚本文件(包含 JavaScript 程序的文本文件)。

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

此处包含的 code/hello.js 文件包含相同的程序——alert("hello!")。当 HTML 页面引用其他 URL 作为其自身的一部分时,例如图像文件或脚本,Web 浏览器会立即检索它们并将其包含在页面中。

即使脚本标签引用了一个脚本文件并且不包含任何代码,也必须始终用 </script> 关闭它。如果您忘记这样做,页面其余部分将被解释为脚本的一部分。

您可以通过为脚本标签提供一个 type="module" 属性来在浏览器中加载 ES 模块(参见 第 10 章)。这些模块可以通过在 import 声明中使用相对于自身的 URL 作为模块名称来依赖其他模块。

某些属性也可以包含 JavaScript 程序。<button> 标签(显示为按钮)支持一个 onclick 属性。每次单击按钮时,都会运行该属性的值。

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

请注意,我不得不使用单引号来引用 onclick 属性中的字符串,因为双引号已经用来引用整个属性。我也可以使用 &quot; 来转义内部引号。

沙盒中

运行从互联网下载的程序可能存在危险。您对访问的大多数网站背后的用户并不了解,他们不一定怀着善意。恶意行为者运行程序是您的计算机感染病毒、数据被盗和帐户被黑客攻击的方式。

然而,网络的吸引力在于,您可以浏览它而不必一定信任您访问的所有页面。这就是为什么浏览器严格限制 JavaScript 程序可以执行的操作:它不能查看您计算机上的文件或修改与它嵌入的网页无关的任何内容。

以这种方式隔离编程环境称为沙盒,其理念是程序在沙盒中安全地玩耍。但您应该想象这种特殊的沙盒顶部有一个厚厚的钢筋笼子,这样在里面玩耍的程序实际上无法逃脱。

沙盒的难点在于,在限制程序执行任何危险操作的同时,为程序提供足够的可用空间。许多有用的功能,例如与其他服务器通信或读取剪贴板的内容,也可以用于存在问题的、侵犯隐私的操作。

不时地,会有人想出一种新的方法来绕过浏览器的限制,并执行一些有害的操作,从泄露少量私人信息到接管运行浏览器的整个机器。浏览器开发人员会通过修复漏洞来做出回应,一切又恢复正常——直到下一个问题被发现,并且希望被公开,而不是被某个政府机构或犯罪组织秘密利用。

兼容性和浏览器大战

在 Web 的早期阶段,名为 Mosaic 的浏览器主导了市场。几年后,平衡转向 Netscape,而 Netscape 又在很大程度上被微软的 Internet Explorer 取代。在任何单一浏览器占主导地位的地方,该浏览器的供应商都会感到有权单方面为 Web 发明新功能。由于大多数用户使用最流行的浏览器,因此网站会简单地开始使用这些功能——无论其他浏览器如何。

这是兼容性黑暗时代,通常被称为浏览器大战。Web 开发人员面临的不是一个统一的 Web,而是两个或三个不兼容的平台。更糟糕的是,大约 2003 年使用的浏览器都充满了错误,当然,每个浏览器的错误都不一样。编写网页的人日子过得很苦。

Mozilla Firefox 是 Netscape 的非营利性分支机构,在 2000 年代后期挑战了 Internet Explorer 的地位。由于微软当时对保持竞争力并不特别感兴趣,因此 Firefox 从微软手中夺走了大量市场份额。大约在同一时间,Google 推出了 Chrome 浏览器,苹果的 Safari 浏览器也获得了普及,导致出现四家主要参与者,而不是一家。

新参与者对标准和更好的工程实践的态度更加认真,为我们带来了更少的兼容性问题和更少的错误。微软看到其市场份额下降,也随之改变了态度,在其取代 Internet Explorer 的 Edge 浏览器中采用了这些态度。如果您现在开始学习 Web 开发,请认为自己很幸运。最新版本的 主要浏览器行为非常一致,并且错误相对较少。

不幸的是,随着 Firefox 的市场份额越来越小,以及 Edge 在 2018 年成为 Chrome 内核的包装程序,这种一致性可能再次以单个供应商(这次是 Google)的形式出现,他们对浏览器市场的控制力足以将他们对 Web 应该是什么样子的想法强加于世界其他地区。

值得一提的是,这长长的历史事件和意外事件链创造了我们今天使用的 Web 平台。在接下来的章节中,我们将为它编写程序。