原文地址:https://javascript.info/formdata

现代JavaScript教程.png

FormData

本章介绍如何发送表单数据,带文件的不带文件的情况都有讲解。

这里会借助 FormData 对象来发送表单数据,它是表单数据的对象化表示。

FormData 是个构造函数,语法如下:

  1. let formData = new FormData([form]);

如果创建时提供了 HTML 表单元素 form,那么生成的 formData 变量中会自动从 form 中捕获到的表单字段数据。

而且 FormData 对象还可以配合 fetch 作为请求的请求体使用,它会自动使用 Content-Type: multipart/form-data 的格式对对象内容进行编码,然后在发送出去。

提交一个简单表单

先从提交一个简单表单开始讲起:

  1. <form id="formElem">
  2. <input type="text" name="name" value="John">
  3. <input type="text" name="surname" value="Smith">
  4. <input type="submit">
  5. </form>
  6. <script>
  7. formElem.onsubmit = async (e) => {
  8. e.preventDefault();
  9. let response = await fetch('/article/formdata/post/user', {
  10. method: 'POST',
  11. body: new FormData(formElem)
  12. });
  13. let result = await response.json();
  14. alert(result.message);
  15. };
  16. </script>

这里没有展示服务端代码,这超出了我们的叙述范围。你只要知道,请求发送出去后,服务端会响应回来一个“User saved”文本即可。

FormData 实例方法

我们可以通过下面的方法,管理 FormData 实例中的表单字段。

  • formData.append(name, value) —— 添加一个表单字段,字段名 name,字段值 vlaue
  • formData.append(name, blob, fileName) —— 添加一个表单字段,内容是二进制文件,等同于提交包含有一个 元素的表单, 字段名 name,字段值 blob(二进制数据),还有指定的文件名 fileName。
  • formData.delete(name) —— 删除表单字段,字段名为 name
  • formData.get(name) —— 获取表单字段值(单个值),字段名为 name
  • formData.getAll(name) —— 获取表单字段值(数组),字段名为 name
  • formData.has(name) —— 是否包含名为 name 的表单字段,有返回 true,无返回 false

技术上讲,某个字段 name 可能对应多个值(例如:复选框组,多选框等。译注),使用 formData.append 方法就能来添加这些多值字段。

还有一个 formData.set(name, value) 方法,与 formData.append(name, value) 有相同的语法。不同之处在于,.set 会移除 name 字段下的所有值,然后再替换为现在的新值。所以,.set 保证同一时间,字段 name 下只唯一存在一个值与之对应。除此之前,跟 append 的使用就完全一样了。

  • formData.set(name, value)
  • formData.set(name, blob, fileName)

还能对 formData 使用 for..of 循环遍历:

  1. let formData = new FormData();
  2. formData.append('key1', 'value1');
  3. formData.append('key2', 'value2');
  4. // List key/value pairs
  5. for(let [name, value] of formData) {
  6. alert(`${name} = ${value}`); // key1=value1, then key2=value2
  7. }

译注:formData.get(name) 与 formData.getAll(name) 有什么区别呢?

看完下面的例子就明白了:

对多值字段来说,用 formData.getAll 获取值更合适。

  1. var formData = new FormData(myForm)
  2. formData.append('foo', 1)
  3. formData.append('foo', 2)
  4. formData.get('foo') // 1
  5. formData.getAll('foo') // [1, 2]

提交带文件的表单

使用 FormData 对象提交的请求,总是会以 Content-Type: multipart/form-data 形式对内容做编码,这种编码是支持发送文件的。就像一次平常的携带 文件提交的表单一样。

下面是使用 FormData 对象提交文件的一个例子:

  1. <form id="formElem">
  2. <input type="text" name="firstName" value="John">
  3. Picture: <input type="file" name="picture" accept="image/*">
  4. <input type="submit">
  5. </form>
  6. <script>
  7. formElem.onsubmit = async (e) => {
  8. e.preventDefault();
  9. let response = await fetch('/article/formdata/post/user-avatar', {
  10. method: 'POST',
  11. body: new FormData(formElem)
  12. });
  13. let result = await response.json();
  14. alert(result.message);
  15. };
  16. </script>

跟上面提交简单表单时使用的 JS 对比,没有修改,只是 HTML 添加了 而已。

发送二进制文件

在上一篇 Fetch API 使用入门中已介绍过,通过 fetch 的 body 参数,我们可以很容易将二进制数据发送出去,比如:使用 Blob 格式发送的图片。

实践中,通常不会在一次请求中只发送一张图片,还能还带有别的什么字段,作为发送的表单数据的一部分被提交。同时,服务器通常更适合接受多部分编码(multipart-encoded forms)的形式,而不仅仅是原始的二进制数据。

下例中,从 获取要提交的图片,并且会携带其他字段信息,并使用 FormData 提交。

  1. <body style="margin:0">
  2. <canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
  3. <input type="button" value="Submit" onclick="submit()">
  4. <script>
  5. canvasElem.onmousemove = function(e) {
  6. let ctx = canvasElem.getContext('2d');
  7. ctx.lineTo(e.clientX, e.clientY);
  8. ctx.stroke();
  9. };
  10. async function submit() {
  11. let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
  12. let formData = new FormData();
  13. formData.append("firstName", "John");
  14. formData.append("image", imageBlob, "image.png");
  15. let response = await fetch('/article/formdata/post/image-form', {
  16. method: 'POST',
  17. body: formData
  18. });
  19. let result = await response.json();
  20. alert(result.message);
  21. }
  22. </script>
  23. </body>

注意,这里是如何添加 Blob 文件的:

  1. formData.append("image", imageBlob, "image.png");

这种方式类似于表单里有一个 在里面,用户提交了一个叫 “image.png”(第三个参数)的文件,使用了数据 imageBlob(第二个参数)。

服务端读取这种方式提交的数据,当成是一次普通的表单提交。

总结

FormData 对象可以用来捕获 HTML 表单中的字段数据,并使用 fetch 或其他网络方法(比如 XMLHttpRequest)提交表单数据。

new FormData(form) 里的 form 是可选的。得到 formData 之后,可以使用下面的方法做字段数据添加:

  • formData.append(name, value)
  • formData.append(name, blob, fileName)
  • formData.set(name, value)
  • formData.set(name, blob, fileName)

这里有两点需要注意的是:

  1. 与 append 不同的是,set 方法在设置字段值之前,会将之前所有改字段名下的字段值全部删除,再替换为现在的新值。
  2. 发送文件,需要使用三个参数值语法,最后一个参数指定的是文件名。

其他方法还有:

  • formData.delete(name)
  • formData.get(name)
  • formData.getAll(name)
  • formData.has(name)

好了,就是这些了!

(完)