第 12 章JavaScript 和浏览器
本书的下一部分将讨论网络浏览器。如果没有网络浏览器,就不会有 JavaScript。即使有,也永远不会有人关注它。
从一开始,网络技术就是去中心化的,不仅从技术上,而且从其演变方式上也是如此。不同的浏览器供应商以即席的方式添加了新的功能,有时这些功能考虑不周,后来有时会被其他人采用,最终成为标准。
这既是福也是祸。一方面,没有中央机构控制系统,而是由不同机构松散合作(或偶尔公开敌对)来改进系统,这是赋权的。另一方面,网络的偶然发展方式意味着由此产生的系统并非内部一致性的典范。事实上,其中一些部分非常混乱和令人困惑。
网络和互联网
计算机网络自 20 世纪 50 年代就存在。如果在两台或多台计算机之间连接电缆,并允许它们通过这些电缆来回发送数据,那么你可以做各种神奇的事情。
如果将同一建筑物中的两台机器连接起来就能让我们做神奇的事情,那么连接遍布全球的机器应该会更好。实现这一愿景的技术是在 20 世纪 80 年代开发的,由此产生的网络被称为互联网。它不负众望。
计算机可以使用此网络将比特发送到另一台计算机。为了使任何有效的通信从这些比特流中产生,两端的计算机必须知道这些比特应该代表什么。任何给定比特序列的含义完全取决于它试图表达的事物类型以及所使用的编码机制。
网络协议描述了一种通过网络进行通信的风格。有用于发送电子邮件、获取电子邮件、共享文件,甚至用于控制可能被恶意软件感染的计算机的协议。
例如,一个简单的聊天协议可能由一台计算机发送代表文本“CHAT?”的比特到另一台机器,另一台机器用“OK!”来响应,以确认它理解协议。然后,它们可以继续相互发送文本字符串,从网络中读取对方发送的文本,并在屏幕上显示收到的任何内容。
大多数协议都建立在其他协议之上。我们的示例聊天协议将网络视为一个流式设备,你可以在其中放置比特,并让它们以正确的顺序到达正确的目的地。确保这些事情本身就是一个相当困难的技术问题。
传输控制协议 (TCP) 是一个解决这个问题的协议。所有连接到互联网的设备都“使用”它,互联网上的大多数通信都建立在它之上。
TCP 连接的工作原理如下:一台计算机必须等待或监听其他计算机开始与它通信。为了能够在同一台机器上同时监听不同类型的通信,每个监听器都有一个与其关联的数字(称为端口)。大多数协议指定默认情况下应该使用哪个端口。例如,当我们想要使用 SMTP 协议发送电子邮件时,我们发送电子邮件的机器预期在端口 25 上监听。
然后,另一台计算机可以通过使用正确的端口号连接到目标机器来建立连接。如果目标机器可以被访问并且正在该端口上监听,那么连接将成功创建。监听的计算机被称为服务器,连接的计算机被称为客户端。
这种连接充当可以流动比特的双向管道——两端的机器都可以将数据放入其中。一旦比特成功传输,就可以被另一端的机器再次读取。这是一个方便的模型。可以说 TCP 提供了网络的抽象。
网络
万维网(不要与整个互联网混淆)是一组协议和格式,允许我们在浏览器中访问网页。名称中的“网络”部分是指这些页面可以轻松地相互链接,从而连接成用户可以浏览的巨大网格。
要将内容添加到网络,你所要做的就是将一台机器连接到互联网,并让它在端口 80 上监听,使用超文本传输协议 (HTTP)。此协议允许其他计算机通过网络请求文档。
网络上的每个文档都由一个统一资源定位符 (URL) 命名,它看起来像这样
https://eloquent.javascript.ac.cn/12_browser.html | | | | protocol server path
第一部分告诉我们此 URL 使用 HTTP 协议(与例如加密的 HTTP(即https://)不同)。然后是识别我们正在向哪个服务器请求文档的部分。最后是一个路径字符串,用于识别我们感兴趣的特定文档(或资源)。
连接到互联网的每台机器都会获得一个唯一的IP 地址,它看起来像37.187.37.82
。你可以直接使用它们作为 URL 的服务器部分。但是,更多或更少的随机数字列表很难记住,而且难以输入,因此你可以注册一个域名来指向特定机器或一组机器。我注册了eloquentjavascript.net 来指向我控制的机器的 IP 地址,因此可以使用该域名来提供网页。
如果在浏览器的地址栏中键入上一个 URL,它将尝试检索并显示该 URL 的文档。首先,浏览器必须找出eloquentjavascript.net 指向哪个地址。然后,使用 HTTP 协议,它与该地址上的服务器建立连接,并请求资源/12_browser.html。
我们将在第 17 章中更详细地了解 HTTP 协议。
HTML
HTML 代表超文本标记语言,是用于网页的文档格式。HTML 文档包含文本以及标签,这些标签为文本提供结构,描述链接、段落和标题等内容。
<html> <head> <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 文档有一个头部和一个主体。头部包含有关文档的信息,主体包含文档本身。在本例中,我们首先声明此文档的标题是“我的主页”,然后给出一个包含标题(<h1>
,表示“标题 1”——<h2>
到 <h6>
会产生更次要的标题)和两个段落(<p>
)的文档。
标签有多种形式。一个元素,例如主体、段落或链接,由一个开始标签(如<p>
)开始,由一个结束标签(如</p>
)结束。一些开始标签,例如链接的标签(<a>
),在标签中包含以name="value"
对形式的额外信息。这些称为属性。在本例中,链接的目标用href="https://eloquent.javascript.ac.cn"
指示,其中href
代表“超文本引用”。
某些类型的标签不包含任何内容,因此不需要关闭。一个例子是<img src="http://example.com/image.jpg">
,它将显示在给定源 URL 处找到的图像。
为了能够在文档的文本中包含尖括号,即使它们在 HTML 中具有特殊含义,也必须引入另一种特殊符号。一个普通的开始尖括号写成<
(“小于”),一个结束尖括号写成>
(“大于”)。在 HTML 中,一个与号 (&
) 字符后跟一个单词和一个分号 (;
) 被称为实体,它将被它编码的字符替换。
这类似于在 JavaScript 字符串中使用反斜杠的方式。由于这种机制也赋予了与号字符特殊含义,因此它们需要转义为&
。在属性内部,属性用双引号括起来,"
可以用来插入一个实际的引号字符。
HTML 以一种非常容错的方式解析。当应该存在的标签缺失时,浏览器会重建它们。重建的方式已经标准化,你可以依赖所有现代浏览器以相同的方式进行重建。
<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>
标签完全消失了。浏览器知道<title>
属于头部,<h1>
属于主体。此外,我不再显式地关闭段落,因为打开一个新段落或结束文档会隐式地关闭它们。链接目标周围的引号也消失了。
本书通常会从示例中省略<html>
、<head>
和 <body>
标签,以使它们简短且没有混乱。但我会关闭标签并在属性周围包含引号。
我通常也会省略文档类型声明。这并不是鼓励你省略文档类型声明。浏览器在遗漏它们时往往会做一些荒唐的事情。即使在示例中没有实际显示文档类型声明,你也应该认为它们是隐含存在的。
HTML 和 JavaScript
在本书的语境下,最重要的 HTML 标签是 <script>
。此标签允许我们在文档中包含一段 JavaScript 代码。
<h1>Testing alert</h1> <script>alert("hello!");</script>
此类脚本将在浏览器读取 HTML 时遇到其 <script>
标签后立即运行。前面显示的页面在打开时会弹出一个 alert
对话框。
直接在 HTML 文档中包含大型程序通常不切实际。<script>
标签可以被赋予一个 src
属性,以便从 URL 获取一个脚本文件(包含 JavaScript 程序的文本文件)。
<h1>Testing alert</h1> <script src="code/hello.js"></script>
这里包含的 code/hello.js 文件包含了相同的简单程序,alert("hello!")
。当 HTML 页面将其他 URL 作为其一部分引用时,例如图像文件或脚本,Web 浏览器会立即检索它们并将它们包含在页面中。
即使脚本标签引用了一个脚本文件且不包含任何代码,也必须始终用 </script>
关闭它。如果你忘记了这一点,页面中剩余的部分将被解释为脚本的一部分。
某些属性也可以包含 JavaScript 程序。下面显示的 <button>
标签(显示为按钮)有一个 onclick
属性,其内容将在每次单击按钮时运行。
<button onclick="alert('Boom!');">DO NOT PRESS</button>
请注意,我必须在 onclick
属性中使用单引号来引号字符串,因为双引号已用于引号整个属性。我也可以使用 "
,但这会使程序更难阅读。
沙盒中
运行从互联网下载的程序可能很危险。你对访问的大多数网站背后的用户知之甚少,他们不一定怀有好意。运行那些怀有不善意的人的程序会导致你的计算机被病毒感染,你的数据被盗,你的帐户被黑。
然而,网络的吸引力在于你可以浏览它,而不必信任你访问的所有页面。这就是浏览器严格限制 JavaScript 程序可以做的事情的原因:它不能查看你的计算机上的文件,也不能修改与它嵌入的网页无关的任何内容。
以这种方式隔离编程环境称为 沙盒,其思想是该程序在沙盒中安全地玩耍。但你应该想象这种特殊的沙盒上有一个厚厚的钢条笼子,这使得它与你典型的游乐场沙盒有所不同。
沙盒最难的部分是允许程序有足够的空间发挥作用,同时限制它们做任何危险的事情。许多有用的功能,例如与其他服务器通信或读取剪贴板的内容,也可以用于做一些有问题的、侵犯隐私的事情。
不时地,有人会想出绕过浏览器限制并做一些有害事情的新方法,从泄露少量私人信息到接管浏览器运行的整个机器。浏览器开发人员会通过修补漏洞来应对,一切又会恢复正常,直到下一个问题被发现,并且希望被公布,而不是被某个政府或黑帮秘密利用。
兼容性和浏览器大战
在 Web 的早期阶段,一款名为 Mosaic 的浏览器主导了市场。几年后,平衡转向了 Netscape,然后 Netscape 又被微软的 Internet Explorer 大量取代。在任何一个单一浏览器占主导地位的时期,该浏览器的供应商都会认为自己有权单方面为 Web изобретать новые功能。由于大多数用户使用相同的浏览器,网站就会开始使用这些功能,而不会理会其他浏览器。
这是兼容性的黑暗时代,通常被称为 浏览器大战。Web 开发人员没有一个统一的 Web,而是面对着两个或三个不兼容的平台。更糟糕的是,大约在 2003 年使用的浏览器都充满了漏洞,而且当然每个浏览器的漏洞都不一样。对于编写网页的人来说,生活很艰难。
Mozilla Firefox,一个非营利性的 Netscape 分支,在 2000 年代后期挑战了 Internet Explorer 的霸权地位。由于微软当时对保持竞争力并不特别感兴趣,Firefox 从微软那里抢走了相当一部分市场份额。大约在同一时间,谷歌推出了其 Chrome 浏览器,苹果的 Safari 浏览器也获得了流行,导致出现了四家主要参与者,而不是一家。
新参与者对标准和更佳的工程实践持更加严肃的态度,导致了更少的兼容性问题和更少的漏洞。微软看到自己的市场份额不断萎缩,开始改变了态度。如果你现在开始学习 Web 开发,你应该庆幸。主流浏览器的最新版本的行为非常一致,并且漏洞相对较少。
但这并不意味着现状已经完美。一些使用 Web 的用户,由于惯性或公司政策的原因,仍然停留在非常旧的浏览器上。在这些浏览器完全消失之前,编写能够为它们工作的网站将需要大量的关于它们的不足之处和怪癖的奥秘知识。本书不是关于这些怪癖的。相反,它旨在介绍现代的、合理的 Web 编程风格。