通过 JavaScript 发送表单

当用户提交 HTML 表单时,例如通过点击 提交按钮,浏览器会向指定的 HTTP 请求发送表单中的数据。但与这种声明式的方法不同,Web 应用有时会使用 JavaScript API,如 fetch(),以编程方式将数据发送到期望表单提交的端点。本文将解释为什么这是一个重要的用例以及如何实现它。

为什么使用 JavaScript 提交表单数据?

正如我们在关于 发送表单数据 的文章中所述,标准的 HTML 表单提交会加载数据发送到的 URL,这意味着浏览器窗口会进行整页刷新导航。

然而,许多 Web 应用,特别是 渐进式 Web 应用单页应用,使用 JavaScript API 从服务器请求数据并更新页面上相关部分,从而避免了整页刷新的开销。

因此,当这些 Web 应用想要提交表单数据时,它们仅使用 HTML 表单来收集用户输入,而不是用于数据提交。当用户尝试发送数据时,应用程序会接管控制权,并使用 JavaScript API(如 fetch())发送数据。

JavaScript 表单提交的问题

如果 Web 应用发送表单数据的服务器端点在 Web 应用开发者的控制之下,那么他们可以按任意方式发送表单数据:例如,作为 JSON 对象。

但是,如果服务器端点期望表单提交,Web 应用必须以特定方式编码数据。例如,如果数据只是文本,它将由 URL 编码的键/值对列表组成,并以 application/x-www-form-urlencodedContent-Type 发送。如果表单包含二进制数据,则必须使用 multipart/form-data 内容类型发送。

FormData 接口负责以这种方式编码数据的过程,在本文的其余部分,我们将快速介绍 FormData。有关更多详细信息,请参阅我们关于 使用 FormData 对象 的指南。

手动构建 FormData 对象

您可以通过为要添加的每个字段调用对象的 append() 方法来填充 FormData 对象,传入字段的名称和值。值可以是字符串(用于文本字段)或 Blob(用于二进制字段,包括 File 对象)。

在下面的示例中,当用户点击按钮时,我们将数据作为表单提交发送

js
async function sendData(data) {
  // Construct a FormData instance
  const formData = new FormData();

  // Add a text field
  formData.append("name", "Pomegranate");

  // Add a file
  const selection = await window.showOpenFilePicker();
  if (selection.length > 0) {
    const file = await selection[0].getFile();
    formData.append("file", file);
  }

  try {
    const response = await fetch("https://example.org/post", {
      method: "POST",
      // Set the FormData instance as the request body
      body: formData,
    });
    console.log(await response.json());
  } catch (e) {
    console.error(e);
  }
}

const send = document.querySelector("#send");
send.addEventListener("click", sendData);
  1. 我们首先构造一个新、空的 FormData 对象。

  2. 接下来,我们调用两次 append(),将两个项目添加到 FormData 对象中:一个文本字段和一个文件。

  3. 最后,我们使用 fetch() API 发起一个 POST 请求,将 FormData 对象设置为请求体。

请注意,我们无需设置 Content-Type 标头:当我们向 fetch() 传递 FormData 对象时,会自动设置正确的标头。

关联 FormData 对象和 <form>

如果您的数据实际上来自一个 <form>,您可以通过将表单传递给 FormData 构造函数来填充 FormData 实例。

假设我们的 HTML 声明了一个 <form> 元素

html
<form id="userinfo">
  <p>
    <label for="username">Enter your name:</label>
    <input type="text" id="username" name="username" value="Dominic" />
  </p>
  <p>
    <label for="avatar">Select an avatar</label>
    <input type="file" id="avatar" name="avatar" required />
  </p>
  <input type="submit" value="Submit" />
</form>

该表单包含一个文本输入框、一个文件输入框和一个提交按钮。

JavaScript 代码如下

js
const form = document.querySelector("#userinfo");

async function sendData() {
  // Associate the FormData object with the form element
  const formData = new FormData(form);

  try {
    const response = await fetch("https://example.org/post", {
      method: "POST",
      // Set the FormData instance as the request body
      body: formData,
    });
    console.log(await response.json());
  } catch (e) {
    console.error(e);
  }
}

// Take over form submission
form.addEventListener("submit", (event) => {
  event.preventDefault();
  sendData();
});

我们为表单元素添加了一个提交事件处理程序。它首先调用 preventDefault() 来阻止浏览器内置的表单提交,以便我们接管。然后我们调用 sendData(),它会检索表单元素并将其传递给 FormData 构造函数。

之后,我们使用 fetch()FormData 实例作为 HTTP POST 请求发送。