第 13 章JavaScript 和浏览器
网络背后的梦想是一个共同的信息空间,我们通过共享信息来交流。 它的通用性至关重要:超文本链接可以指向任何事物,无论是个人、本地还是全球,无论是草稿还是高度抛光。
本书接下来的章节将讨论网络浏览器。 没有网络浏览器,就没有 JavaScript。 即使有,也没有人会注意到它。
网络技术从一开始就实现了去中心化,不仅在技术上,而且在演变方式上。 各种浏览器供应商以临时的方式,有时是经过糟糕考虑的方式添加了新功能,然后,有时,这些功能最终被其他人采用,并最终被写入标准。
这既是福也是祸。 一方面,没有中心方控制系统,而是由各种方在松散的协作(或偶尔公开的敌对)中改进系统,这是一种赋权。 另一方面,网络开发的随意方式意味着最终产生的系统并非内部一致性的典范。 其中一些部分 downright 令人困惑且设计糟糕。
网络和互联网
计算机网络自 1950 年代就已存在。 如果你在两台或多台计算机之间放置电缆并允许它们通过这些电缆来回发送数据,你就可以做各种各样奇妙的事情。
如果连接同一建筑物中的两台机器允许我们做奇妙的事情,那么连接遍布全球的机器应该会更好。 在 1980 年代,开始实施这一愿景的技术得到了发展,由此产生的网络被称为互联网。 它已经实现了它的承诺。
一台计算机可以使用此网络将比特发送到另一台计算机。 为了让任何有效的通信从这种比特射击中产生,两端的计算机都必须知道比特应该代表什么。 任何给定比特序列的含义完全取决于它试图表达的事物的类型以及使用的编码机制。
网络协议描述了通过网络的通信方式。 有用于发送电子邮件、获取电子邮件、共享文件甚至控制被恶意软件感染的计算机的协议。
例如,超文本传输协议 (HTTP) 是一种用于检索命名资源(信息块,例如网页或图片)的协议。 它指定发出请求的一方应该以类似以下的代码行开始,命名资源及其尝试使用的协议版本
GET /index.html HTTP/1.1
关于请求者如何将更多信息包含在请求中以及返回资源的另一方如何打包其内容的方式还有很多规则。 我们将在 第 18 章 中更详细地介绍 HTTP。
大多数协议都建立在其他协议之上。 HTTP 将网络视为一个类似流的设备,你可以将比特放入其中,并使它们按正确顺序到达正确目的地。 正如我们在 第 11 章 中看到的,确保这些事情已经是相当困难的问题。
传输控制协议 (TCP) 是一种解决此问题的协议。 所有连接到互联网的设备都“说”这种协议,并且互联网上的大多数通信都是建立在它之上的。
TCP 连接的工作方式如下:一台计算机必须等待或监听其他计算机开始与其通信。 为了能够在单台机器上同时监听不同类型的通信,每个监听者都有一个与其关联的数字(称为端口)。 大多数协议都指定默认情况下应使用哪个端口。 例如,当我们想要使用 SMTP 协议发送电子邮件时,我们通过它发送电子邮件的机器预计将监听端口 25。
然后,另一台计算机可以通过使用正确的端口号连接到目标机器来建立连接。 如果目标机器可以访问并且正在监听该端口,则连接成功建立。 监听计算机称为服务器,连接计算机称为客户端。
这种连接充当一个双向管道,比特可以通过它流动 - 两端的机器都可以将数据放入其中。 比特成功传输后,另一端的机器可以再次读出它们。 这是一个方便的模型。 你可以说 TCP 提供了网络的抽象。
网络
万维网(不要与整个互联网混淆)是一组协议和格式,允许我们在浏览器中访问网页。 名称中的“网络”部分指的是这些页面可以轻松地相互链接,从而连接成用户可以浏览的巨大网络。
要成为网络的一部分,你所要做的就是将一台机器连接到互联网,并让它使用 HTTP 协议监听端口 80,以便其他计算机可以向它请求文档。
网络上的每个文档都由一个统一资源定位符 (URL) 命名,它看起来像这样
https://eloquent.javascript.ac.cn/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,代表超文本标记语言,是用于网页的文档格式。 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://
表示,其中href
代表“超文本引用”。
某些类型的标签不包含任何内容,因此不需要关闭。 元数据标签<meta charset="utf-8">
就是一个例子。
为了能够在文档文本中包含尖括号,即使它们在 HTML 中具有特殊含义,也必须引入另一种特殊的符号。 一个简单的开始尖括号写成<
(“小于”),一个结束括号写成>
(“大于”)。 在 HTML 中,一个与号 (&
) 字符后跟一个名称或字符代码和一个分号 (;
) 被称为实体,并将被它编码的字符替换。
这类似于 JavaScript 字符串中使用反斜杠的方式。由于这种机制也赋予了与符号特殊含义,因此需要将其转义为&
。在用双引号括起来的属性值内,可以使用"
插入实际的引号字符。
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>
标签,以保持示例简短且无杂乱。但我将关闭标签并在属性周围包含引号。
我还通常会省略文档类型和charset
声明。这并不是要鼓励您从 HTML 文档中删除这些内容。当您忘记它们时,浏览器通常会做一些荒谬的事情。您应该将文档类型和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 作为自身的一部分引用时 - 例如,图像文件或脚本 - 网页浏览器会立即检索它们并将它们包含在页面中。
即使脚本标签引用了脚本文件并且不包含任何代码,也必须始终使用 </script>
关闭它。如果您忘记了这一点,页面其余部分将被解释为脚本的一部分。
您可以通过为脚本标签指定type="module"
属性来在浏览器中加载 ES 模块(参见第 10 章)。这样的模块可以通过在import
声明中使用相对于它们自己的 URL 作为模块名称来依赖于其他模块。
一些属性也可以包含 JavaScript 程序。下面显示的<button>
标签(显示为按钮)有一个onclick
属性。每次单击按钮时,属性的值都会运行。
<button onclick="alert('Boom!');">DO NOT PRESS</button>
请注意,我必须对onclick
属性中的字符串使用单引号,因为双引号已经用于引用整个属性。我也可以使用"
。
在沙箱中
运行从互联网下载的程序可能很危险。您对访问的大多数网站背后的用户了解不多,他们不一定怀有好意。运行怀有不轨意图的用户的程序会导致您的计算机感染病毒、数据被盗以及帐户被盗。
然而,网络的吸引力在于您可以浏览它,而无需必信赖访问的所有页面。这就是为什么浏览器严格限制 JavaScript 程序可以执行的操作的原因:它不能查看计算机上的文件或修改与嵌入它的网页无关的任何内容。
以这种方式隔离编程环境称为沙箱,其理念是程序在沙箱中玩耍是无害的。但是,您应该将这种特殊的沙箱想象成有一个厚厚的钢筋笼罩在它上面,以便在其中玩耍的程序无法真正逃脱。
沙箱的难点在于让程序有足够的空间发挥作用,但同时也要限制它们做任何危险的事情。许多有用的功能,例如与其他服务器通信或读取剪贴板内容,也可以用于做有问题的、侵犯隐私的事情。
每隔一段时间,就会有人想出一种新的方法来规避浏览器的限制,并做一些有害的事情,从泄露少量私人信息到接管浏览器运行的整个机器。浏览器开发人员会通过修复漏洞来做出回应,一切都会恢复正常 - 直到发现下一个问题,并希望将其公布于众,而不是被某个政府机构或黑手党秘密利用。
兼容性和浏览器大战
在网络的早期阶段,一款名为 Mosaic 的浏览器主导了市场。几年后,平衡转向了 Netscape,然后,反过来,它在很大程度上被微软的 Internet Explorer 取代。在任何单一浏览器占主导地位的地方,该浏览器的供应商都会认为自己有权单方面为网络发明新功能。由于大多数用户使用最流行的浏览器,因此网站会简单地开始使用这些功能 - 不管其他浏览器如何。
这是兼容性的黑暗时代,通常被称为浏览器大战。网页开发人员发现自己面对的不是一个统一的网络,而是两个或三个不兼容的平台。更糟糕的是,大约 2003 年使用的浏览器都充满了漏洞,而且当然每个浏览器的漏洞都不一样。对于编写网页的人来说,生活很艰难。
Mozilla Firefox 是 Netscape 的一个非营利性分支机构,它在 2000 年代后期挑战了 Internet Explorer 的地位。由于微软当时对保持竞争力并不特别感兴趣,因此 Firefox 从中抢走了很多市场份额。大约在同一时间,谷歌推出了其 Chrome 浏览器,苹果的 Safari 浏览器也越来越受欢迎,这导致了出现四个主要参与者而不是一个参与者的局面。
新的参与者对标准和更好的工程实践的态度更加认真,这使得我们有了更少的兼容性问题和更少的错误。微软看到其市场份额不断下降,便回过神来,在 Edge 浏览器中采用了这些态度,Edge 浏览器取代了 Internet Explorer。如果您今天开始学习网页开发,请算您幸运。主要浏览器的最新版本表现相当一致,并且漏洞相对较少。