JavaScript 编程精解 中文第三版 十8、HTTP 和表单

来源: ApacheCN『JavaScript 编程精解 中文第三版』翻译项目

原文:HTTP and Formsjavascript

译者:飞龙html

协议:CC BY-NC-SA 4.0java

自豪地采用谷歌翻译git

部分参考了《JavaScript 编程精解(第 2 版)》github

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

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

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

协议

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

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 开头的状态码表示服务器端出现了问题,而请求没有问题。

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

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

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

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

请求和响应可能都会在协议头后包含一个空行,后面则是消息体,包含所发送的数据。GETDELETE请求不单独发送任何数据,但PUTPOST请求则会。一样地,一些响应类型(如错误响应)不须要有消息体。

浏览器和 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 提供了encodeURIComponentdecodeURIComponent函数来按照这种格式进行编码和解码。

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 应用程序中使用表单和字段做为积木。

一个网页表单在其<form>标签中包含若干个输入字段。HTML 容许多个的不一样风格的输入字段,从简单的开关选择框到下拉菜单和进行输入的字段。本书不会全面的讨论每个输入字段类型,不过咱们会先大概讲述一下。

不少字段类型都使用<input>标签。标签的type属性用来选择字段的种类,下面是一些经常使用的<input>类型。

  • text:一个单行的文本输入框。
  • password:和text相同但隐藏了输入内容。
  • checkbox:一个复选框。
  • radio:一个多选择字段中的一个单选框。
  • file:容许用户从本机选择文件上传。

表单字段并不必定要出如今<form>标签中。你能够把表单字段放置在一个页面的任何地方。但这样不带表单的字段不能被提交(一个完整的表单才能够),当须要和 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>,这样作是由于经过一个属性来声明一个多行初始值会十分奇怪。<textarea>要求有一个相匹配的</textarea>结束标签并使用标签之间的文本做为初始值,而不是使用value属性存储文本。

<textarea>
one
two
three
</textarea>

<select>标签用来创造一个可让用户从一些提早设定好的选项中进行选择的字段。

<select>
  <option>Pancakes</option>
  <option>Pudding</option>
  <option>Ice cream</option>
</select>

当一个表单字段中的内容更改时会触发change事件。

聚焦

不一样于 HTML 文档中的其余元素,表单字段能够获取键盘焦点。当点击或以某种方式激活时,他们会成为激活的元素,并接受键盘的输入。

所以,只有得到焦点时,你才能输入文本字段。 其余字段对键盘事件的响应不一样。 例如,<select>菜单尝试移动到包含用户输入文本的选项,并经过向上和向下移动其选项来响应箭头键。

咱们能够经过使用 JavaScript 的focusblur方法来控制聚焦。第一个会聚焦到某一个 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>

禁用的字段不能拥有焦点或更改,浏览器使它们变成灰色。

当一个程序在处理一些由按键或其余控制方式出发的事件,而且这些事件可能要求和服务器的通讯时,将元素禁用直到动做完成多是一个很好的方法。按照这用方式,当用户失去耐心而且再次点击时,不会意外的重复这一动做。

做为总体的表单

当一个字段被包含在<form>元素中时,其 DOM 元素会有一个form属性指向form的 DOM 元素。<form>元素则会有一个叫做elements属性,包含一个相似于数据的集合,其中包含所有的字段。

一个表单字段的name属性会决定在form提交时其内容的辨别方式。同时在获取formelements属性时也能够做为一种属性名,因此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键也会有一样的效果。

一般在提交一个表单时,浏览器会将页面导航到formaction属性指明的页面,使用GETPOST请求。可是在这些发生以前,"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属性为textpassword<input>标签和textarea标签组成的字段有相同的接口。其 DOM 元素都有一个value属性,保存了为字符串格式的当前内容。将这个属性更改成另外一个值将改变字段的内容。

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

和正常的值同样,这些属性也能够被更改。

想象你正在编写关于 Knaseknemwy 的文章,可是名字拼写有一些问题,后续代码将<textarea>标签和一个事件处理器关联起来,当点击F2时,插入 Knaseknemwy。

<textarea></textarea>
<script>
  let textarea = document.querySelector("textarea");
  textarea.addEventListener("keydown", event => {
    // The key code for F2 happens to be 113
    if (event.keyCode == 113) {
      replaceSelection(textarea, "Khasekhemwy");
      event.preventDefault();
    }
  });
  function replaceSelection(field, word) {
    let from = field.selectionStart, to = field.selectionEnd;
    field.value = field.value.slice(0, from) + word +
                  field.value.slice(to);
    // Put the cursor after the word
    field.selectionStart = from + word.length;
    field.selectionEnd = from + word.length;
  }
</script>

replaceSelection函数用给定的字符串替换当前选中的文本字段内容,并将光标移动到替换内容后让用户能够继续输入。change事件不会在每次有输入时都被调用,而是在内容在改变并失焦后触发。为了及时的响应文本字段的改变,则须要为input事件注册一个处理器,每当用户有输入或更改时就被触发。

下面的例子展现一个文本字段和一个展现字段中的文字的当前长度的计数器。

<input type="text"> length: <span id="length">0</span>
<script>
  let text = document.querySelector("input");
  let output = document.querySelector("#length");
  text.addEventListener("input", () => {
    output.textContent = text.value.length;
  });
</script>

选择框和单选框

一个选择框只是一个双选切换。其值能够经过其包含一个布尔值的checked属性来获取和更改。

<label>
  <input type="checkbox" id="purple"> Make this page purple
</label>
<script>
  let checkbox = document.querySelector("#purple");
  checkbox.addEventListener("change", () => {
    document.body.style.background =
      checkbox.checked ? "mediumpurple" : "";
  });
</script>

<label>标签关联部分文本和一个输入字段。点击标签上的任何位置将激活该字段,这样会将其聚焦,并当它为复选框或单选按钮时切换它的值。

单选框和选择框相似,不过单选框能够经过相同的name属性,隐式关联其余几个单选框,保证只能选择其中一个。

Color:
<label>
  <input type="radio" name="color" value="orange"> Orange
</label>
<label>
  <input type="radio" name="color" value="lightgreen"> Green
</label>
<label>
  <input type="radio" name="color" value="lightblue"> Blue
</label>
<script>
  let buttons = document.querySelectorAll("[name=color]");
  for (let button of Array.from(buttons)) {
    button.addEventListener("change", () => {
      document.body.style.background = button.value;
    });
  }
</script>

提供给querySelectorAll的 CSS 查询中的方括号用于匹配属性。 它选择name属性为"color"的元素。

选择字段

选择字段和单选按钮比较类似,容许用户从多个选项中选择。可是,单选框的展现排版是由咱们控制的,而<select>标签外观则是由浏览器控制。

选择字段也有一个更相似于复选框列表的变体,而不是单选框。 当赋予multiple属性时,<select>标签将容许用户选择任意数量的选项,而不只仅是一个选项。 在大多数浏览器中,这会显示与正常的选择字段不一样的效果,后者一般显示为下拉控件,仅在你打开它时才显示选项。

每个<option>选项会有一个值,这个值能够经过value属性来定义。若是没有提供,选项内的文本将做为其值。<select>value属性反映了当前的选中项。对于一个多选字段,这个属性用处不太大由于该属性只会给出一个选中项。

<select>字段的<option>标签能够经过一个相似于数组对象的options属性访问到。每一个选项会有一个叫做selected的属性,来代表这个选项当前是否被选中。这个属性能够用来被设定选中或不选中。

这个例子会从多选字段中取出选中的数值,并使用这些数值构造一个二进制数字。按住CTRL(或 Mac 的COMMAND键)来选择多个选项。

<select multiple>
  <option value="1">0001</option>
  <option value="2">0010</option>
  <option value="4">0100</option>
  <option value="8">1000</option>
</select> = <span id="output">0</span>
<script>
  let select = document.querySelector("select");
  let output = document.querySelector("#output");
  select.addEventListener("change", () => {
    let number = 0;
    for (let option of Array.from(select.options)) {
      if (option.selected) {
        number += Number(option.value);
      }
    }
    output.textContent = number;
  });
</script>

文件字段

文件字段最初是用于经过表单来上传从浏览器机器中获取的文件。在现代浏览器中,也能够从 JavaScript 程序中读取文件。该字段则做为一个看门人角色。脚本不能简单地直接从用户的电脑中读取文件,可是若是用户在这个字段中选择了一个文件,浏览器会将这个行为解释为脚本,即可以访问该文件。

一个文本字段是一个相似于“选择文件”或“浏览”标签的按钮,后面跟着所选文件的信息。

<input type="file">
<script>
  let input = document.querySelector("input");
  input.addEventListener("change", () => {
    if (input.files.length > 0) {
      let file = input.files[0];
      console.log("You chose", file.name);
      if (file.type) console.log("It has type", file.type);
    }
  });
</script>

文本字段的files属性是一个类数组对象(固然,不是一个真正的数组),包含在字段中所选择的文件。开始时是空的。所以文本字段属性不只仅是file属性。有时文本字段能够上传多个文件,这使得同时选择多个文件变为可能。

files对象中的对象有name(文件名)、size(文件大小,单位为字节),和type(文件的媒体类型,如text/plainimage/jpeg)等属性。

files属性中不包含文件内容的属性。获取这个内容会比较复杂。因为从硬盘中读取文件会须要一些时间,接口必须是异步的,来避免文档的无响应问题。

<input type="file" multiple>
<script>
  let input = document.querySelector("input");
  input.addEventListener("change", () => {
    for (let file of Array.from(input.files)) {
      let reader = new FileReader();
      reader.addEventListener("load", () => {
        console.log("File", file.name, "starts with",
                    reader.result.slice(0, 20));
      });
      reader.readAsText(file);
    }
  });
</script>

读取文件是经过FileReader对象实现的,注册一个load事件处理器,而后调用readAsText方法,传入咱们但愿读取的文件,一旦载入完成,readerresult属性内容就是文件内容。

FileReader对象还会在读取文件失败时触发error事件。错误对象自己会存在readererror属性中。这个接口是在Promise成为语言的一部分以前设计的。 你能够把它包装在Promise中,像这样:

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);
  });
}

客户端保存数据

采用 JavaScript 代码的简单 HTML 页面能够做为实现一些小应用的很好的途径。能够采用小的帮助程序来自动化一些基本的任务。经过关联一些表单字段和事件处理器,你能够实现华氏度与摄氏度的转换。也能够实现由主密码和网站名来生成密码等各类任务。

当一个应用须要存储一些东西以便于跨对话使用时,则不能使用 JavaScript 绑定由于每当页面关闭时这些值就会丢失。你能够搭建一个服务器,链接到因特网,将一些服务数据存储到其中。在第20章中将会介绍如何实现这些,固然这须要不少的工做,也有必定的复杂度。有时只要将数据存储在浏览器中便可。

localStorage对象能够用于保存数据,它在页面从新加载后还存在。这个对象容许你将字符串存储在某个名字(也是字符串)下,下面是具体示例。

localStorage.setItem("username", "marijn");
console.log(localStorage.getItem("username"));
// → marijn
localStorage.removeItem("username");

一个在localStorage中的值会保留到其被重写时,它也能够经过removeItem来清除,或者由用户清除本地数据。

不一样字段名的站点的数据会存在不一样的地方。这也代表原则上由localStorage存储的数据只能够由相同站点的脚本编辑。

浏览器的确限制一个站点能够存储的localStorage的数据大小。这种限制,以及用垃圾填满人们的硬盘并非真正有利可图的事实,防止该特性占用太多空间。

下面的代码实现了一个粗糙的笔记应用。程序将用户的笔记保存为一个对象,将笔记的标题和内容字符串相关联。对象被编码为 JSON 格式并存储在localStorage中。用户能够从<select>选择字段中选择笔记并在<textarea>中编辑笔记,并能够经过点击一个按钮来添加笔记。

Notes: <select></select> <button>Add</button><br>
<textarea style="width: 100%"></textarea>

<script>
  let list = document.querySelector("select");
  let note = document.querySelector("textarea");

  let state;
  function setState(newState) {
    list.textContent = "";
    for (let name of Object.keys(newState.notes)) {
      let option = document.createElement("option");
      option.textContent = name;
      if (newState.selected == name) option.selected = true;
      list.appendChild(option);
    }
    note.value = newState.notes[newState.selected];

    localStorage.setItem("Notes", JSON.stringify(newState));
    state = newState;
   }
  setState(JSON.parse(localStorage.getItem("Notes")) || {
    notes: {"shopping list": "Carrots\nRaisins"},
    selected: "shopping list"
  });
  }

  list.addEventListener("change", () => {
    setState({notes: state.notes, selected: list.value});
  });
  note.addEventListener("change", () => {
    setState({
      notes: Object.assign({}, state.notes,
                           {[state.selected]: note.value}),
      selected: state.selected
    });
  });

  document.querySelector("button")
    .addEventListener("click", () => {
      let name = prompt("Note name");
      if (name) setState({
        notes: Object.assign({}, state.notes, {[name]: ""}),
        selected: name
      });
    });
</script>

脚本从存储在localStorage中的"Notes"值来获取它的初始状态,若是其中没有值,它会建立示例状态,仅仅带有一个购物列表。从localStorage中读取不存在的字段会返回null

setState方法确保 DOM 显示给定的状态,并将新状态存储到localStorage。 事件处理器调用这个函数来移动到一个新状态。

在这个例子中使用Object.assign,是为了建立一个新的对象,它是旧的state.notes的一个克隆,可是添加或覆盖了一个属性。 Object.assign选取第一个参数,向其添加全部更多参数的全部属性。 所以,向它提供一个空对象会使它填充一个新对象。 第三个参数中的方括号表示法,用于建立名称基于某个动态值的属性。

还有另外一个和localStorage很类似的对象叫做sessionStorage。这两个对象之间的区别在于sessionStorage的内容会在每次会话结束时丢失,而对于多数浏览器来讲,会话会在浏览器关闭时结束。

本章小结

在本章中,咱们讨论了 HTTP 协议的工做原理。 客户端发送一个请求,该请求包含一个方法(一般是GET)和一个标识资源的路径。 而后服务器决定如何处理请求,并用状态码和响应正文进行响应。 请求和响应均可能包含提供附加信息的协议头。

浏览器 JavaScript 能够经过fetch接口生成 HTTP 请求。 像这样生成请求:

fetch("/18_http.html").then(r => r.text()).then(text => {
  console.log(`The page starts with ${text.slice(0, 15)}`);
});

浏览器生成GET请求来获取显示网页所需的资源。 页面也可能包含表单,这些表单容许在提交表单时,用户输入的信息发送为新页面的请求。

HTML能够表示多种表单字段,例如文本字段、选择框、多选字段和文件选取。

这些字段能够用 JavaScript 进行控制和读取。内容改变时会触发change事件,文本有输入时会触发input事件,键盘得到焦点时触发键盘事件。 例如"value"(用于文本和选择字段)或"checked"(用于复选框和单选按钮)的属性,用于读取或设置字段的内容。

当一个表单被提交时,会触发其submit事件,JavaScript 处理器能够经过调用preventDefault来禁用默认的提交事件。表单字段的元素不必定须要被包装在<form>标签中。

当用户在一个文件选择字段中选择了本机中的一个文件时,能够用FileReader接口来在 JavaScript 中获取文件内容。

localStoragesessionStorage对象能够用来保存页面重载后依旧保留的信息。第一个会永久保留数据(直到用户决定清除),第二个则会保存到浏览器关闭时。

习题

内容协商

HTTP 能够作的事情之一就是内容协商。 Accept请求头用于告诉服务器,客户端想要得到什么类型的文档。 许多服务器忽略这个协议头,可是当一个服务器知道各类编码资源的方式时,它能够查看这个协议头,并发送客户端首选的格式。

URL eloquentjavascript.net/author配置为响应明文,HTML 或 JSON,具体取决于客户端要求的内容。 这些格式由标准化的媒体类型"text/plain""text/html""application/json"标识。

发送请求来获取此资源的全部三种格式。 使用传递给fetchoptions对象中的headers属性,将名为Accept的协议头设置为所需的媒体类型。

最后,请尝试请求媒体类型"application/rainbows+unicorns",并查看产生的状态码。

// Your code here.

JavaScript 工做台

构建一个接口,容许用户输入和运行一段 JavaScript 代码。

<textarea>字段旁边放置一个按钮,当按下该按钮时,使用咱们在第 10 章中看到的Function构造器,将文本包装到一个函数中并调用它。 将函数的返回值或其引起的任何错误转换为字符串,并将其显示在文本字段下。

<textarea id="code">return "hi";</textarea>
<button id="button">Run</button>
<pre id="output"></pre>

<script>
  // Your code here.
</script>

Conway 的生命游戏

Conway 的生命游戏是一个简单的在网格中模拟生命的游戏,每个细胞均可以生存或灭亡。对于每一代(回合),都要遵循如下规则:

  • 任何细胞,周围有少于两个或多于三个的活着的邻居,都会死亡。
  • 任意细胞,拥有两个或三个的活着的邻居,能够生存到下一代。
  • 任何死去的细胞,周围有三个活着的邻居,能够再次复活。

任意一个相连的细胞均可以称为邻居,包括对角相连。

注意这些规则要马上应用于整个网格,而不是一次一个网格。这代表邻居的数目由开始的一代决定,而且邻居在每一代时发生的变化不该该影响给定细胞新的状态。

使用任何一个你认为合适的数据结构来实现这个游戏。使用Math.random来随机的生成开始状态。将其展现为一个选择框组成的网格和一个生成下一代的按钮。当用户选中或取消选中一个选择框时,其变化应该影响下一代的计算。

<div id="grid"></div>
<button id="next">Next generation</button>

<script>
  // Your code here.
</script>
相关文章
相关标签/搜索