1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 11:52:20 +00:00
wizardforcel 50f6978d6b 18.
2018-05-14 11:47:32 +08:00

37 KiB
Raw Blame History

十八、HTTP 和表单

通信在实质上必须是无状态的,从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上存储的任何上下文。

Roy Fielding《Architectural Styles and the Design of Network-based Software Architectures》

我们曾在第 13 章中提到过超文本传输协议HTTP万维网中通过该协议进行数据请求和传输。在本章中会对该协议进行详细介绍并解释浏览器中JavaScript访问HTTP的方式。

协议

当你在浏览器地址栏中输入eloquentjavascript.net/18_http.html时,浏览器会首先找到和eloquentjavascript.net相关的服务器的地址,然后尝试通过 80 端口建立 TCP 连接,其中 80 端口是 HTTP 的默认通信端口。如果该服务器存在并且接受了该连接,浏览器可能发送如下内容。

GET /18_http.html HTTP/1.1
Host: eloquentjavascript.net
User-Agent: Your browser's name

然后服务器会通过同一个链接返回如下内容。

HTTP/1.1 200 OK
Content-Length: 65585
Content-Type: text/html
Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT

<!doctype html>
... the rest of the document

浏览器会选取空行之后的响应部分,也就是正文(不要与 HTML <body>标签混淆),并将其显示为 HTML 文档。

由客户端发出的信息叫作请求。请求的第一行如下。

GET /17_http.html HTTP/1.1

请求中的第一个单词是请求方法。GET表示我们希望得到一个我们指定的资源。其他常用方式还有DELETE用于删除一个资源PUT用于替换资源POST用于发送消息。需要注意的是服务器并不需要处理所有收到的请求。如果你随机访问一个网站并请求删除主页服务器很有可能会拒绝你的请求。

方法名后的请求部分是所请求的资源的路径。在最简单的情况下,一个资源只是服务器中的一个文件。不过,协议并没有要求资源一定是实际文件。一个资源可以是任何可以像文件一样传输的东西。很多服务器会实时地生成这些资源。例如,如果你打开github.com/marijnh,服务器会在数据库中寻找名为marijnjh的用户,如果找到了则会为该用户的生成介绍页面。

请求的第一行中位于资源路径后面的HTTP/1.1用来表明所使用的HTTP协议的版本。

在实践中,许多网站使用 HTTP v2它支持与版本 1.1 相同的概念,但是要复杂得多,因此速度更快。 浏览器在与给定服务器通信时,会自动切换到适当的协议版本,并且无论使用哪个版本,请求的结果都是相同的。 由于 1.1 版更直接,更易于使用,因此我们将专注于此。

服务器的响应也是以版本号开始的。版本号后面是应答状态,首先是一个三位的状态码,然后是一个可读的字符串。

HTTP/1.1 200 OK

以2开头的状态码表示请求成功。以4开头的状态码表示请求中有错误。404是最著名的HTTP状态码了表示找不到资源。以5开头的状态码表示服务器端出现了问题而请求没有问题。

请求或响应的第一行后可能会有任意个协议头多个形如“namevalue”的行表明了和请求或响应相关的更多信息。这些是示例响应中的头信息。

Content-Length: 65585
Content-Type: text/html
Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT

这些信息说明了响应文档的大小和类型。在这个例子中响应是一个65585字节的HTML文档同时也说明了该文档最后的更改时间。

多数大多数协议头客户端或服务器可以自由决定需要在请求或响应中包含的协议头不过也有一些协议头是必需的。例如指明主机名的Host头在请求中是必须的因为一个服务器可能在一个IP地址下有多个主机名服务如果没有Host头服务器则无法判断客户端尝试请求哪个主机。

请求和应答可能都会在协议头后包含一个空行后面则是消息体包含所发送的数据。GET和DELETE请求不单独发送任何数据但PUT和POST请求则会。同样地一些响应类型如错误应答不需要有消息体。

浏览器和 HTTP

正如上例所示当我们在浏览器地址栏输入一个URL后浏览器会发送一个请求。当HTML页面中包含有其他的文件例如图片和JavaScript文件时浏览器也会一并获取这些资源。

一个较为复杂的网站通常都会有10到200个不等的资源。为了可以很快地取得这些资源浏览器会同时发送多个GET请求而不是一次等待一个请求。此类文档都是通过GET方法来获取的。

HTML页面可能包含表单用户可以在表单中填入一些信息然后由浏览器将其发送到服务器。如下是一个表单的例子。

<form method="GET" action="example/message.html">
  <p>Name: <input type="text" name="name"></p>
  <p>Message:<br><textarea name="message"></textarea></p>
  <p><button type="submit">Send</button></p>
</form>

这段代码描述了一个有两个输入字段的表单:较小的输入字段要求用户输入姓名,较大的要求用户输入一条消息。当点击发送按钮时,表单就提交了,这意味着其字段的内容被打包到 HTTP 请求中,并且浏览器跳转到该请求的结果。

<form>元素的method属性是GET(或省略)时,表单中的信息将作为查询字符串添加到action URL 的末尾。 浏览器可能会向此 URL 发出请求:

GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1

问号表示路径的末尾和查询字符串的起始。后面是多个名称和值,这些名称和值分别对应form输入字段中的name属性和这些元素的内容。&字符用来分隔不同的名称对。

在这个 URL 中经过编码的消息实际原本是“Yes只不过浏览器用奇怪的代码替换了问号。我们必须替换掉请求字符串中的一些字符。使用%3F替换的问号就是其中之一。这样看似乎有一个不成文的规定每种格式都会有自己的转义字符。这里的编码格式叫作URL编码使用一个百分号和16进制的数字来对字符进行编码。在这个例子中3F十进制为63是问号字符的编码。JavaScript提供了encodeURIComponent和decodeURIComponent函数来按照这种格式进行编码和解码。

console.log(encodeURIComponent("Yes?"));
// → Yes%3F
console.log(decodeURIComponent("Yes%3F"));
// → Yes?

如果我们将本例HTML表单中的method属性更改为POST则浏览器会使用POST方法发送该表单并将请求字符串放到请求体中而不是添加到URL中。

POST /example/message.html HTTP/1.1
Content-length: 24
Content-type: application/x-www-form-urlencoded

name=Jean&message=Yes%3F

GET请求应该用于没有副作用的请求,而仅仅是询问信息。 可以改变服务器上的某些内容的请求,例如创建一个新帐户或发布消息,应该用其他方法表示,例如POST。 诸如浏览器之类的客户端软件,知道它不应该盲目地发出POST请求,但通常会隐式地发出GET请求 - 例如预先获取一个它认为用户很快需要的资源。

我们将在本章后面的回到表单,以及如何与 JavaScript 交互。

Fetch

浏览器 JavaScript 可以通过Fetch接口生成 HTTP 请求。 由于它比较新,所以它很方便地使用了Promise(这在浏览器接口中很少见)。

fetch("example/data.txt").then(response => {
  console.log(response.status);
  // → 200
  console.log(response.headers.get("Content-Type"));
  // → text/plain
});

调用fetch返回一个Promise,它解析为一个Response对象,该对象包含服务器响应的信息,例如状态码和协议头。 协议头被封装在类Map的对象中,该对象不区分键(协议头名称)的大小写,因为协议头名称不应区分大小写。 这意味着header.get("Content-Type")headers.get("content-TYPE")将返回相同的值。

请注意,即使服务器使用错误代码进行响应,由fetch返回的Promise也会成功解析。 如果存在网络错误或找不到请求的服务器,它也可能被拒绝。

fetch的第一个参数是请求的 URL。 当该 URL 不以协议名称(例如http:)开头时,它被视为相对路径,这意味着它解释为相对于当前文档的路径。 当它以斜线(/)开始时,它将替换当前路径,即服务器名称后面的部分。 否则,当前路径直到并包括最后一个斜杠的部分,放在相对 URL 前面。

为了获取响应的实际内容,可以使用其text方法。 由于初始Promise在收到响应头文件后立即解析,并且读取响应正文可能需要一段时间,这又会返回一个Promise

fetch("example/data.txt")
  .then(resp => resp.text())
  .then(text => console.log(text));
// → This is the content of data.txt

有一种类似的方法,名为json,它返回一个Promise,它将解析为,将正文解析为 JSON 时得到的值,或者不是有效的 JSON则被拒绝。

默认情况下,fetch使用GET方法发出请求,并且不包含请求正文。 你可以通过传递一个带有额外选项的对象作为第二个参数,来进行不同的配置。 例如,这个请求试图删除example/data.txt

fetch("example/data.txt", {method: "DELETE"}).then(resp => {
  console.log(resp.status);
  // → 405
});

405 状态码意味着“方法不允许”,这是 HTTP 服务器说“我不能这样做”的方式。

为了添加一个请求正文,你可以包含body选项。 为了设置标题,存在headers选项。 例如,这个请求包含Range协议,它指示服务器只返回一部分响应。

fetch("example/data.txt", {headers: {Range: "bytes=8-19"}})
  .then(resp => resp.text())
  .then(console.log);
// → the content

览器将自动添加一些请求头,例如"Host"和服务器需要的协议头,来确定正文的大小。 但是对于包含认证信息或告诉服务器想要接收的文件格式,添加自己的协议头通常很有用。

HTTP 沙箱

在网页脚本中发出 HTTP 请求,再次引发了安全性的担忧。 控制脚本的人的兴趣可能不同于正在运行的计算机的所有者。 更具体地说,如果我访问themafia.org,我不希望其脚本能够使用来自我的浏览器的身份向mybank.com发出请求,并且下令将我所有的钱转移到某个随机帐户。

出于这个原因,浏览器通过禁止脚本向其他域(如themafia.orgmybank.com等名称)发送 HTTP 请求来保护我们。

在构建希望因合法原因访问多个域的系统时,这可能是一个恼人的问题。 幸运的是,服务器可以在响应中包含这样的协议头,来明确地向浏览器表明,请求可以来自另一个域:

Access-Control-Allow-Origin: *

运用 HTTP

当构建一个需要让浏览器客户端的JavaScript程序和服务器端的程序进行通信的系统时有一些不同的方式可以实现这个功能。

一个常用的方法是远程过程调用,通信遵从正常的方法调用方式,不过调用的方法实际运行在另一台机器中。调用包括向服务器发送包含方法名和参数的请求。响应的结果则包括函数的返回值。

当考虑远程过程调用时HTTP只是通信的载体并且你很可能会写一个抽象层来隐藏细节。

另一个方法是使用一些资源和HTTP方法来建立自己的通信。不同于远程调用方法addUser你需要发送一个PUT请求到users/larry不同于将用户属性进行编码后作为参数传递你定义了一个 JSON 文档格式或使用一种已有的格式来展示一个用户。PUT请求的正文则只是这样的一个用来建立新资源的文档。由GET方法获取的资源则是自愿的URL例如/users/larry该URL返回代表这个资源的文档。

第二种方法使用了HTTP的一些特性所以使得整体更简洁。例如对于资源缓存的支持在客户端存一份副本用于快速访问。HTTP中使用的概念设计良好可以提供一组有用的原则来设计服务器接口。

安全和 HTTPS

通过互联网传播的数据,往往走过漫长而危险的道路。 为了到达目的地,它必须跳过任何东西,从咖啡店的 Wi-Fi 到由各个公司和国家管理的网络。 在它的路线上的任何位置,它都可能被探测或者甚至被修改。

如果对某件事保密是重要的,例如您的电子邮件帐户的密码,或者它到达目的地而未经修改是重要的,例如帐户号码,您使用它在银行网站上转账,纯 HTTP 就不够好了。

安全的 HTTP 协议,其 URL 以https://开头是一种难以阅读和篡改的HTTP 流量的封装方式。 在交换数据之前,客户端证实该服务器是它所声称的东西,通过要求它证明,它具有由浏览器承认的证书机构所颁发的证书。 接下来,通过连接传输的所有数据,都将以某种方式加密,它应该防止窃听和篡改。

因此,当 HTTPS 正常工作时,它可以阻止某人冒充您想要与之通话的网站,以及某人窥探您的通信。 这并不完美,由于伪造或被盗的证书和损坏的软件,存在各种 HTTPS 失败的事故,但它比纯 HTTP 更安全。

表单字段

表单最初是为 JavaScript 之前的网页设计的,允许网站通过 HTTP 请求发送用户提交的信息。 这种设计假定与服务器的交互,总是通过导航到新页面实现。

但是它们的元素是 DOM 的一部分,就像页面的其他部分一样,并且表示表单字段的 DOM 元素,支持许多其他元素上不存在的属性和事件。 这些使其可以使用 JavaScript 程序检查和控制这些输入字段,以及可以执行一些操作,例如向表单添加新功能,或在 JavaScript 应用程序中使用表单和字段作为积木。

一个网页表单在其

标签中包含若干个输入字段。HTML允许多个的不同风格的输入字段从简单的开关选择框到下拉菜单和进行输入的字段。本书不会全面的讨论每一个输入字段类型不过我们会先大概讲述一下。

很多字段类型都使用标签。标签的type属性用来选择字段的种类下面是一些常用的类型。

  • text一个单行的文本输入框。

  • password和text相同但隐藏了输入内容。

  • checkbox一个复选框。

  • radio一个多选择字段中的一个单选框。

  • file允许用户从本机选择文件上传。

表单字段并不一定要出现在标签中。你可以把表单字段放置在一个页面的任何地方。但这样不带表单的字段不能被提交一个完整的表单才可以当需要和JavaScript进行响应时我们通常也不希望按常规的方式提交表单。

<p><input type="text" value="abc"> (text)</p>
<p><input type="password" value="abc"> (password)</p>
<p><input type="checkbox" checked> (checkbox)</p>
<p><input type="radio" value="A" name="choice">
   <input type="radio" value="B" name="choice" checked>
   <input type="radio" value="C" name="choice"> (radio)</p>
<p><input type="file"> (file)</p>

这些元素的 JavaScript 接口和元素类型不同。

多行文本输入框有其自己的标签,这样做是因为通过一个属性来声明一个多行初始值会十分奇怪。<textarea>要求有一个相匹配的结束标签并使用标签之间的文本作为初始值而不是使用value属性存储文本。

<textarea>
one
two
three
</textarea>

标签用来创造一个可以让用户从一些提前设定好的选项中进行选择的字段。 <select> <option>Pancakes</option> <option>Pudding</option> <option>Ice cream</option> </select> 当一个表单字段中的内容更改时会触发change事件。 聚焦 不同于HTML文档中的其他元素表单字段可以获取键盘焦点。当点击或以某种方式激活时他们会成为激活的元素并接受键盘的输入。 因此,只有获得焦点时,你才能输入文本字段。 其他字段对键盘事件的响应不同。 例如,<select>菜单尝试移动到包含用户输入文本的选项,并通过向上和向下移动其选项来响应箭头键。 我们可以通过使用JavaScript的focus和blur方法来控制聚焦。第一个会聚焦到某一个DOM元素第二个则使其失焦。在document.activeElement中的值会关联到当前聚焦的元素。 <input type="text"> <script> document.querySelector("input").focus(); console.log(document.activeElement.tagName); // → INPUT document.querySelector("input").blur(); console.log(document.activeElement.tagName); // → BODY </script> 对于一些页面用户希望立刻使用到一个表单字段。JavaScript可以在页面载入完成时将焦点放到这些字段上HTML提供了autofocus属性可以实现相同的效果并让浏览器知道我们正在尝试实现的事情。这向浏览器提供了选项来禁用一些错误的操作例如用户希望将焦点置于其他地方。 浏览器也允许用户通过TAB键来切换焦点。通过tabindex属性可以改变元素接受焦点的顺序。后面的例子会让焦点从文本输入框之间跳转到OK按钮而不是到帮助链接。 <input type="text" tabindex=1> <a href=".">(help)</a> <button onclick="console.log('ok')" tabindex=2>OK</button> 默认情况下多数的HTML元素不能被聚焦。但是可以通过添加tabindex属性使任何元素可聚焦。tabindex为 -1 使 TAB 键跳过元素,即使它通常是可聚焦的。 禁用字段 所有的表单字段都可以通过其disable属性来禁用。它是一个可以被指定为没有值的属性 - 事实上它出现在所有禁用的元素中。 <button>I'm all right</button> <button disabled>I'm out</button> 禁用的字段不能被聚焦或更改,浏览器使它们变成灰色。 当一个程序在处理一些由按键或其他控制方式出发的事件,并且这些事件可能要求和服务器的通信时,将元素禁用直到动作完成可能是一个很好的方法。按照这用方式,当用户失去耐心并且再次点击时,不会意外的重复这一动作。 作为整体的表单 当一个字段被包含在元素中时其DOM元素会有一个form属性指向form的DOM元素。元素则会有一个叫作elements属性包含一个类似于数据的集合其中包含全部的字段。 一个表单字段的name属性会决定在form提交时其内容的辨别方式。同时在获取form的elements属性时也可以作为一种属性名所以elements属性既可以像数组由编号来访问一样使用也可以像映射一样访问通过名字访问。 <form action="example/submit.html"> Name: <input type="text" name="name"><br> Password: <input type="password" name="password"><br> <button type="submit">Log in</button> </form> <script> let form = document.querySelector("form"); console.log(form.elements[1].type); // → password console.log(form.elements.password.type); // → password console.log(form.elements.name.form == form); // → true </script> 一个type属性为submit的按钮在点击时会提交表单。在一个form被聚焦时点击enter键也会有同样的效果。 通常在提交一个表单时浏览器会将页面导航到form的action属性指明的页面使用GET或POST请求。但是在这些发生之前“submit”事件会被触发。这个事件可以由JavaScript处理并且处理函数可以通过调用事件对象的preventDefault来禁用默认行为。 <form action="example/submit.html"> Value: <input type="text" name="value"> <button type="submit">Save</button> </form> <script> let form = document.querySelector("form"); form.addEventListener("submit", event => { console.log("Saving value", form.elements.value.value); event.preventDefault(); }); </script> 在JavaScript中submit事件有多种用途。我们可以编写代码来检测用户输入是否正确并且立刻提示错误信息而不是提交表单。或者我们可以禁用正常的提交方式正如这个例子中让我们的程序处理输入可能使用fetch将其发送到服务器而不重新加载页面。 文本字段 由type属性为text或password的标签和textarea标签组成的字段有相同的接口。其DOM元素都有一个value属性保存了为字符串格式的当前内容。将这个属性更改为另一个值将改变字段的内容。

文本字段selectionStart和selectEnd属性包含光标和所选文字的信息。当没有选中文字时这两个属性的值相同表明当前光标的信息。例如0表示文本的开始10表示光标在第十个字符之后。当一部分字段被选中时这两个属性值会不同表明选中文字开始位置和结束位置。

和正常的值一样,这些属性也可以被更改。

想象你正在编写关于Knaseknemwy的文章但是名字拼写有一些问题后续代码将标签和一个事件处理器关联起来当点击F2时插入Knaseknemwy。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">textarea</span><span class="p">></</span><span class="nt">textarea</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">textarea</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"textarea"</span><span class="p">);</span> <span class="nx">textarea</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"keydown"</span><span class="p">,</span> <span class="nx">event</span> <span class="p">=></span> <span class="p">{</span> <span class="c1">// The key code for F2 happens to be 113 </span><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">keyCode</span> <span class="o">==</span> <span class="mi">113</span><span class="p">)</span> <span class="p">{</span> <span class="nx">replaceSelection</span><span class="p">(</span><span class="nx">textarea</span><span class="p">,</span> <span class="s2">"Khasekhemwy"</span><span class="p">);</span> <span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span> <span class="p">}</span> <span class="p">});</span> <span class="kd">function</span> <span class="nx">replaceSelection</span><span class="p">(</span><span class="nx">field</span><span class="p">,</span> <span class="nx">word</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">from</span> <span class="o">=</span> <span class="nx">field</span><span class="p">.</span><span class="nx">selectionStart</span><span class="p">,</span> <span class="nx">to</span> <span class="o">=</span> <span class="nx">field</span><span class="p">.</span><span class="nx">selectionEnd</span><span class="p">;</span> <span class="nx">field</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">field</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">from</span><span class="p">)</span> <span class="o">+</span> <span class="nx">word</span> <span class="o">+</span> <span class="nx">field</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">to</span><span class="p">);</span> <span class="c1">// Put the cursor after the word </span><span class="c1"></span> <span class="nx">field</span><span class="p">.</span><span class="nx">selectionStart</span> <span class="o">=</span> <span class="nx">from</span> <span class="o">+</span> <span class="nx">word</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">field</span><span class="p">.</span><span class="nx">selectionEnd</span> <span class="o">=</span> <span class="nx">from</span> <span class="o">+</span> <span class="nx">word</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><p dir="auto">replaceSelection函数用给定的字符串替换当前选中的文本字段内容并将光标移动到替换内容后让用户可以继续输入。Change事件不会在每次有输入时都被调用而是在内容在改变并失焦后触发。为了及时的响应文本字段的改变则需要为input事件注册一个处理函数每当用户有输入或更改时就被触发。</p> <p dir="auto">下面的例子展示一个文本字段和一个展示字段中的文字的当前长度的计数器。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"text"</span><span class="p">></span> length: <span class="p"><</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">"length"</span><span class="p">></span>0<span class="p"></</span><span class="nt">span</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">text</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"input"</span><span class="p">);</span> <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"#length"</span><span class="p">);</span> <span class="nx">text</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"input"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">output</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">text</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="p">});</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><h2 id="user-content-选择框和单选框" dir="auto">选择框和单选框</h2> <p dir="auto">一个选择框只是一个双选切换。其值可以通过其包含一个布尔值的checked属性来获取和更改。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"checkbox"</span> <span class="na">id</span><span class="o">=</span><span class="s">"purple"</span><span class="p">></span> Make this page purple <span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">checkbox</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"#purple"</span><span class="p">);</span> <span class="nx">checkbox</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">background</span> <span class="o">=</span> <span class="nx">checkbox</span><span class="p">.</span><span class="nx">checked</span> <span class="o">?</span> <span class="s2">"mediumpurple"</span> <span class="o">:</span> <span class="s2">""</span><span class="p">;</span> <span class="p">});</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><p dir="auto"><label>标签关联部分文本和一个输入字段。点击标签上的任何位置将激活该字段,这样会将其聚焦,并当它为复选框或单选按钮时切换它的值。</p> <p dir="auto">单选框和选择框类似不过单选框可以通过相同的name属性隐式的和其他几个单选框关联保证只能选择其中一个。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display">Color: <span class="p"><</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"radio"</span> <span class="na">name</span><span class="o">=</span><span class="s">"color"</span> <span class="na">value</span><span class="o">=</span><span class="s">"orange"</span><span class="p">></span> Orange <span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"radio"</span> <span class="na">name</span><span class="o">=</span><span class="s">"color"</span> <span class="na">value</span><span class="o">=</span><span class="s">"lightgreen"</span><span class="p">></span> Green <span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"radio"</span> <span class="na">name</span><span class="o">=</span><span class="s">"color"</span> <span class="na">value</span><span class="o">=</span><span class="s">"lightblue"</span><span class="p">></span> Blue <span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">buttons</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">"[name=color]"</span><span class="p">);</span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">button</span> <span class="k">of</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">buttons</span><span class="p">))</span> <span class="p">{</span> <span class="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">background</span> <span class="o">=</span> <span class="nx">button</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span> <span class="p">});</span> <span class="p">}</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><p dir="auto">提供给<code>querySelectorAll</code>的 CSS 查询中的方括号用于匹配属性。 它选择<code>name</code>属性为<code>"color"</code>的元素。</p> <h2 id="user-content-选择字段" dir="auto">选择字段</h2> <p dir="auto">选择字段和单选按钮比较相似,允许用户从多个选项中选择。但是,单选框的展示排版是由我们控制的,而<select>标签外观则是由浏览器控制。</p> <p dir="auto">选择字段也有一个更类似于复选框列表的变体,而不是单选框。 当赋予<code>multiple</code>属性时,<code><select></code>标签将允许用户选择任意数量的选项,而不仅仅是一个选项。 在大多数浏览器中,这会显示与正常的选择字段不同的效果,后者通常显示为下拉控件,仅在您打开它时才显示选项。</p> <p dir="auto">每一个<option>选项会有一个值这个值可以通过value属性来定义。如果没有提供选项内的文本将作为其值。<select>的value属性反映了当前的选中项。对于一个多选字段这个属性用处不太大因为该属性只会给出一个选中项。</p> <p dir="auto"><select>字段的<option>标签可以通过一个类似于数组对象的options属性访问到。每个选项会有一个叫作selected的属性来表明这个选项当前是否被选中。这个属性可以用来被设定选中或不选中。</p> <p dir="auto">这个例子会从多选字段中取出选中的数值并使用这些数值构造一个二进制数字。按住CTRL或Mac的COMMAND键来选择多个选项。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">select</span> <span class="na">multiple</span><span class="p">></span> <span class="p"><</span><span class="nt">option</span> <span class="na">value</span><span class="o">=</span><span class="s">"1"</span><span class="p">></span>0001<span class="p"></</span><span class="nt">option</span><span class="p">></span> <span class="p"><</span><span class="nt">option</span> <span class="na">value</span><span class="o">=</span><span class="s">"2"</span><span class="p">></span>0010<span class="p"></</span><span class="nt">option</span><span class="p">></span> <span class="p"><</span><span class="nt">option</span> <span class="na">value</span><span class="o">=</span><span class="s">"4"</span><span class="p">></span>0100<span class="p"></</span><span class="nt">option</span><span class="p">></span> <span class="p"><</span><span class="nt">option</span> <span class="na">value</span><span class="o">=</span><span class="s">"8"</span><span class="p">></span>1000<span class="p"></</span><span class="nt">option</span><span class="p">></span> <span class="p"></</span><span class="nt">select</span><span class="p">></span> = <span class="p"><</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">"output"</span><span class="p">></span>0<span class="p"></</span><span class="nt">span</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">select</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"select"</span><span class="p">);</span> <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"#output"</span><span class="p">);</span> <span class="nx">select</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">number</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">option</span> <span class="k">of</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">select</span><span class="p">.</span><span class="nx">options</span><span class="p">))</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">option</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span> <span class="p">{</span> <span class="nx">number</span> <span class="o">+=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">option</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">output</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">number</span><span class="p">;</span> <span class="p">});</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><h2 id="user-content-文件字段" dir="auto">文件字段</h2> <p dir="auto">文件字段最初是用于通过表单来上传从浏览器机器中获取的文件。在现代浏览器中也可以从JavaScript程序中读取文件。该字段则作为一个看门人角色。脚本不能简单地直接从用户的电脑中读取文件但是如果用户在这个字段中选择了一个文件浏览器会将这个行为解释为脚本便可以访问该文件。</p> <p dir="auto">一个文本字段是一个类似于“选择文件”或“浏览”标签的按钮,后面跟着所选文件的信息。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"file"</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"input"</span><span class="p">);</span> <span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">files</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">file</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"You chose"</span><span class="p">,</span> <span class="nx">file</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"It has type"</span><span class="p">,</span> <span class="nx">file</span><span class="p">.</span><span class="nx">type</span><span class="p">);</span> <span class="p">}</span> <span class="p">});</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><p dir="auto">文本字段的files属性是一个类似数组的对象当然不是一个真正的数组包含在字段中所选择的文件。开始时是空的。因此文本字段属性不仅仅是file属性。有时文本字段可以上传多个文件这使得同时选择多个文件变为可能。</p> <p dir="auto">files对象中的对象有name文件名、size文件大小单位为字节和type文件的媒体类型如text/plainimage/jped等属性。</p> <p dir="auto">而files属性中不包含文件内容的属性。获取这个内容会比较复杂。由于从硬盘中读取文件会需要一些时间接口必须是异步的来避免文档的无响应问题。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"file"</span> <span class="na">multiple</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"input"</span><span class="p">);</span> <span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">file</span> <span class="k">of</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">files</span><span class="p">))</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span> <span class="nx">reader</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"load"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"File"</span><span class="p">,</span> <span class="nx">file</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="s2">"starts with"</span><span class="p">,</span> <span class="nx">reader</span><span class="p">.</span><span class="nx">result</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">));</span> <span class="p">});</span> <span class="nx">reader</span><span class="p">.</span><span class="nx">readAsText</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span> <span class="p">}</span> <span class="p">});</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><p dir="auto">读取文件是通过FileReader对象实现的注册一个load事件处理器然后调用readAsText方法传入我们希望读取的文件一旦载入完成reader的result属性内容就是文件内容。</p> <p dir="auto">FileReader对象还会在读取文件失败时触发error事件。错误对象本身会存在reader的error属性中。这个接口是在<code>Promise</code>成为语言的一部分之前设计的。 你可以把它包装在<code>Promise</code>中,像这样:</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display">function readFileText(file) { return new Promise((resolve, reject) => { let reader = new FileReader(); reader.addEventListener( "load", () => resolve(reader.result)); reader.addEventListener( "error", () => reject(reader.error)); }); reader.readAsText(file); }); } </code></pre><h2 id="user-content-客户端保存数据" dir="auto">客户端保存数据</h2> <p dir="auto">采用JavaScript代码的简单HTML页面可以作为实现一些小应用的很好的途径。可以采用小的帮助程序来自动化一些基本的任务。通过关联一些表单字段和事件处理函数你可以实现华氏度与摄氏度的转换。也可以实现由主密码和网站名来生成密码等各种任务。</p> <p dir="auto">当一个应用需要存储一些东西以便于跨对话使用时则不能使用JavaScript绑定因为每当页面关闭时这些值就会丢失。你可以搭建一个服务器连接到因特网将一些服务数据存储到其中。在第20章中将会介绍如何实现这些当然这需要很多的工作也有一定的复杂度。有时只要将数据存储在浏览器中即可。</p> <p dir="auto"><code>localStorage</code>对象可以用于保存数据,它在页面重新加载后还存在。这个对象允许你将字符串存储在某个名字(也是字符串)下,下面是具体示例。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-js display"><span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s2">"username"</span><span class="p">,</span> <span class="s2">"marijn"</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s2">"username"</span><span class="p">));</span> <span class="c1">// → marijn </span><span class="c1"></span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="s2">"username"</span><span class="p">);</span> </code></pre><p dir="auto">一个在localStorage中的值会保留到其被重写时它也可以通过removeItem来清除或者由用户清除本地数据。</p> <p dir="auto">不同字段名的站点的数据会存在不同的地方。这也表明原则上由localStorage存储的数据只可以由相同站点的脚本编辑。</p> <p dir="auto">浏览器的确限制一个站点可以存储的localStorage的数据大小。这种限制以及用垃圾填满人们的硬盘并不是真正有利可图的事实防止该特性占用太多空间。</p> <p dir="auto">下面的代码实现了一个粗糙的笔记应用。程序将用户的笔记保存为一个对象将笔记的标题和内容字符串相关联。对象被编码为JSON格式并存储在localStorage中。用户可以从<select>选择字段中选择笔记并在<textarea>中编辑笔记,并可以通过点击一个按钮来添加笔记。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display">Notes: <span class="p"><</span><span class="nt">select</span><span class="p">></</span><span class="nt">select</span><span class="p">></span> <span class="p"><</span><span class="nt">button</span><span class="p">></span>Add<span class="p"></</span><span class="nt">button</span><span class="p">><</span><span class="nt">br</span><span class="p">></span> <span class="p"><</span><span class="nt">textarea</span> <span class="na">style</span><span class="o">=</span><span class="s">"width: 100%"</span><span class="p">></</span><span class="nt">textarea</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="kd">let</span> <span class="nx">list</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"select"</span><span class="p">);</span> <span class="kd">let</span> <span class="nx">note</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"textarea"</span><span class="p">);</span> <span class="kd">let</span> <span class="nx">state</span><span class="p">;</span> <span class="kd">function</span> <span class="nx">setState</span><span class="p">(</span><span class="nx">newState</span><span class="p">)</span> <span class="p">{</span> <span class="nx">list</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">name</span> <span class="k">of</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">newState</span><span class="p">.</span><span class="nx">notes</span><span class="p">))</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">option</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"option"</span><span class="p">);</span> <span class="nx">option</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">newState</span><span class="p">.</span><span class="nx">selected</span> <span class="o">==</span> <span class="nx">name</span><span class="p">)</span> <span class="nx">option</span><span class="p">.</span><span class="nx">selected</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="nx">list</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">option</span><span class="p">);</span> <span class="p">}</span> <span class="nx">note</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">newState</span><span class="p">.</span><span class="nx">notes</span><span class="p">[</span><span class="nx">newState</span><span class="p">.</span><span class="nx">selected</span><span class="p">];</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s2">"Notes"</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">newState</span><span class="p">));</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">newState</span><span class="p">;</span> <span class="p">}</span> <span class="nx">setState</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s2">"Notes"</span><span class="p">))</span> <span class="o">||</span> <span class="p">{</span> <span class="nx">notes</span><span class="o">:</span> <span class="p">{</span><span class="s2">"shopping list"</span><span class="o">:</span> <span class="s2">"Carrots\nRaisins"</span><span class="p">},</span> <span class="nx">selected</span><span class="o">:</span> <span class="s2">"shopping list"</span> <span class="p">});</span> <span class="p">}</span> <span class="nx">list</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">setState</span><span class="p">({</span><span class="nx">notes</span><span class="o">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">notes</span><span class="p">,</span> <span class="nx">selected</span><span class="o">:</span> <span class="nx">list</span><span class="p">.</span><span class="nx">value</span><span class="p">});</span> <span class="p">});</span> <span class="nx">note</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"change"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">setState</span><span class="p">({</span> <span class="nx">notes</span><span class="o">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span> <span class="nx">state</span><span class="p">.</span><span class="nx">notes</span><span class="p">,</span> <span class="p">{[</span><span class="nx">state</span><span class="p">.</span><span class="nx">selected</span><span class="p">]</span><span class="o">:</span> <span class="nx">note</span><span class="p">.</span><span class="nx">value</span><span class="p">}),</span> <span class="nx">selected</span><span class="o">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">selected</span> <span class="p">});</span> <span class="p">});</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"button"</span><span class="p">)</span> <span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"click"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">prompt</span><span class="p">(</span><span class="s2">"Note name"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="nx">setState</span><span class="p">({</span> <span class="nx">notes</span><span class="o">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span> <span class="nx">state</span><span class="p">.</span><span class="nx">notes</span><span class="p">,</span> <span class="p">{[</span><span class="nx">name</span><span class="p">]</span><span class="o">:</span> <span class="s2">""</span><span class="p">}),</span> <span class="nx">selected</span><span class="o">:</span> <span class="nx">name</span> <span class="p">});</span> <span class="p">});</span> <span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><p dir="auto">脚本从存储在localStorage中的<code>"Notes"</code>值来获取它的初始状态如果其中没有值它会创建示例状态仅仅带有一个购物列表。从localStorage中读取不存在的字段会返回null。</p> <p dir="auto"><code>setState</code>方法确保 DOM 显示给定的状态,并将新状态存储到<code>localStorage</code>。 事件处理器调用这个函数来移动到一个新状态。</p> <p dir="auto">在这个例子中使用<code>Object.assign</code>,是为了创建一个新的对象,它是旧的<code>state.notes</code>的一个克隆,但是添加或覆盖了一个属性。 <code>Object.assign</code>选取第一个参数,向其添加所有更多参数的所有属性。 因此,向它提供一个空对象会使它填充一个新对象。 第三个参数中的方括号表示法,用于创建名称基于某个动态值的属性。</p> <p dir="auto">还有另一个和localStorage很相似的对象叫作sessionStorage。这两个对象之间的区别在于sessionStorage的内容会在每次会话结束时丢失而对于多数浏览器来说会话会在浏览器关闭时结束。</p> <h2 id="user-content-本章小结" dir="auto">本章小结</h2> <p dir="auto">在本章中,我们讨论了 HTTP 协议的工作原理。 客户端发送一个请求,该请求包含一个方法(通常是<code>GET</code>)和一个标识资源的路径。 然后服务器决定如何处理请求,并用状态码和响应正文进行响应。 请求和响应都可能包含提供附加信息的协议头。</p> <p dir="auto">浏览器 JavaScript 可以通过<code>fetch</code>接口生成 HTTP 请求。 像这样生成请求:</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-js display"><span class="nx">fetch</span><span class="p">(</span><span class="s2">"/18_http.html"</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="p">=></span> <span class="nx">r</span><span class="p">.</span><span class="nx">text</span><span class="p">()).</span><span class="nx">then</span><span class="p">(</span><span class="nx">text</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`The page starts with </span><span class="si">${</span><span class="nx">text</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">15</span><span class="p">)</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span> <span class="p">});</span> </code></pre><p dir="auto">浏览器生成<code>GET</code>请求来获取显示网页所需的资源。 页面也可能包含表单,这些表单允许在提交表单时,用户输入的信息发送为新页面的请求。</p> <p dir="auto">HTML可以表示多种表单字段例如文本字段、选择框、多选字段和文件选取。</p> <p dir="auto">这些字段可以用 JavaScript 进行控制和读取。内容改变时会触发change事件文本有输入时会触发input事件键盘获得焦点时触发键盘事件。 例如“value”用于文本和选择字段或“checked”用于复选框和单选按钮的属性用于读取或设置字段的内容。</p> <p dir="auto">当一个表单被提交时会触发其submit事件JavaScript处理器可以通过调用preventDefault来禁用默认的提交事件。表单字段的元素不一定需要被包装在<form>标签中。</p> <p dir="auto">当用户在一个文件选择字段中选择了本机中的一个文件时可以用FileReader接口来在JavaScript中获取文件内容。</p> <p dir="auto">localStorage和sessionStorage对象可以用来保存页面重载后依旧保留的信息。第一个会永久保留数据直到用户决定清楚第二个则会保存到浏览器关闭时。</p> <h2 id="user-content-习题" dir="auto">习题</h2> <h3 id="user-content-内容协商" dir="auto">内容协商</h3> <p dir="auto">HTTP 可以做的事情之一就是内容协商。 <code>Accept</code>请求头用于告诉服务器,客户端想要获得什么类型的文档。 许多服务器忽略这个协议头,但是当一个服务器知道各种编码资源的方式时,它可以查看这个协议头,并发送客户端首选的格式。</p> <p dir="auto">URL <code>eloquentjavascript.net/author</code>配置为响应明文HTML 或 JSON具体取决于客户端要求的内容。 这些格式由标准化的媒体类型“text / plain”“text / html”和“application / json”标识。</p> <p dir="auto">发送请求来获取此资源的所有三种格式。 使用传递给<code>fetch</code>的<code>options</code>对象中的<code>headers</code>属性,将名为<code>Accept</code>的协议头设置为所需的媒体类型。</p> <p dir="auto">最后请尝试请求媒体类型“application/rainbows+unicorns”并查看产生的状态码。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-js display"><span class="c1">// Your code here. </span></code></pre><h3 id="user-content-javascript-工作台" dir="auto">JavaScript 工作台</h3> <p dir="auto">构建一个接口,允许用户输入和运行一段 JavaScript 代码。</p> <p dir="auto">在<code><textarea></code>字段旁边放置一个按钮,当按下该按钮时,使用我们在第 10 章中看到的<code>Function</code>构造器,将文本包装到一个函数中并调用它。 将函数的返回值或其引发的任何错误转换为字符串,并将其显示在文本字段下。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">textarea</span> <span class="na">id</span><span class="o">=</span><span class="s">"code"</span><span class="p">></span>return "hi";<span class="p"></</span><span class="nt">textarea</span><span class="p">></span> <span class="p"><</span><span class="nt">button</span> <span class="na">id</span><span class="o">=</span><span class="s">"button"</span><span class="p">></span>Run<span class="p"></</span><span class="nt">button</span><span class="p">></span> <span class="p"><</span><span class="nt">pre</span> <span class="na">id</span><span class="o">=</span><span class="s">"output"</span><span class="p">></</span><span class="nt">pre</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="c1">// Your code here. </span><span class="c1"></span><span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre><h3 id="user-content-conway-的生命游戏" dir="auto">Conway 的生命游戏</h3> <p dir="auto">Conway的生命游戏是一个简单的在方格中模拟生命的游戏每一个小格都可以生存或灭亡。对于每一代生命都要遵循以下条件</p> <ul dir="auto"> <li> <p dir="auto">任何小格周围有少于两个或多于三个的活着的邻居都会死亡。</p> </li> <li> <p dir="auto">任意两个或三个的活着的邻居的小格可以生存到下一代。</p> </li> <li> <p dir="auto">任何死去的、有三个活着的邻居的小格可以再次复活。</p> </li> </ul> <p dir="auto">任意一个相连的小格都可以称为邻居,包括对角相连。</p> <p dir="auto">注意这些规则要立刻应用于整个网格,而不是一次一个方格。这表明邻居的数目由开始的一代决定,并且邻居在每一代时发生的变化不应该影响给定小格新的状态。</p> <p dir="auto">使用任何一个你认为合适的数据结构来实现这个游戏。使用Math.random来随机的生成开始状态。将其展示为一个选择框组成的网格和一个生成下一代的按钮。当用户选中或取消选中一个选择框时其变化应该影响下一代的计算。</p> <pre data-attr-class="Tlg3mzeW-gu2B3OG:code-block"><code data-attr-class="Tlg3mzeW-gu2B3OG:chroma language-html display"><span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"grid"</span><span class="p">></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">button</span> <span class="na">id</span><span class="o">=</span><span class="s">"next"</span><span class="p">></span>Next generation<span class="p"></</span><span class="nt">button</span><span class="p">></span> <span class="p"><</span><span class="nt">script</span><span class="p">></span> <span class="c1">// Your code here. </span><span class="c1"></span><span class="p"></</span><span class="nt">script</span><span class="p">></span> </code></pre></body></html>