FormData
本章介绍如何发送表单数据,带文件的不带文件的情况都有讲解。
这里会借助 FormData 对象来发送表单数据,它是表单数据的对象化表示。
FormData 是个构造函数,语法如下:
let formData = new FormData([form]);
如果创建时提供了 HTML 表单元素 form,那么生成的 formData 变量中会自动从 form 中捕获到的表单字段数据。
而且 FormData 对象还可以配合 fetch 作为请求的请求体使用,它会自动使用 Content-Type: multipart/form-data 的格式对对象内容进行编码,然后在发送出去。
提交一个简单表单
先从提交一个简单表单开始讲起:
<form id="formElem">
<input type="text" name="name" value="John">
<input type="text" name="surname" value="Smith">
<input type="submit">
</form>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let response = await fetch('/article/formdata/post/user', {
method: 'POST',
body: new FormData(formElem)
});
let result = await response.json();
alert(result.message);
};
</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 循环遍历:
let formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');
// List key/value pairs
for(let [name, value] of formData) {
alert(`${name} = ${value}`); // key1=value1, then key2=value2
}
译注:formData.get(name) 与 formData.getAll(name) 有什么区别呢?
看完下面的例子就明白了:
对多值字段来说,用 formData.getAll 获取值更合适。
var formData = new FormData(myForm)
formData.append('foo', 1)
formData.append('foo', 2)
formData.get('foo') // 1
formData.getAll('foo') // [1, 2]
提交带文件的表单
使用 FormData 对象提交的请求,总是会以 Content-Type: multipart/form-data 形式对内容做编码,这种编码是支持发送文件的。就像一次平常的携带 文件提交的表单一样。
下面是使用 FormData 对象提交文件的一个例子:
<form id="formElem">
<input type="text" name="firstName" value="John">
Picture: <input type="file" name="picture" accept="image/*">
<input type="submit">
</form>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let response = await fetch('/article/formdata/post/user-avatar', {
method: 'POST',
body: new FormData(formElem)
});
let result = await response.json();
alert(result.message);
};
</script>
跟上面提交简单表单时使用的 JS 对比,没有修改,只是 HTML 添加了 而已。
发送二进制文件
在上一篇 Fetch API 使用入门中已介绍过,通过 fetch 的 body 参数,我们可以很容易将二进制数据发送出去,比如:使用 Blob 格式发送的图片。
实践中,通常不会在一次请求中只发送一张图片,还能还带有别的什么字段,作为发送的表单数据的一部分被提交。同时,服务器通常更适合接受多部分编码(multipart-encoded forms)的形式,而不仅仅是原始的二进制数据。
下例中,从
<body style="margin:0">
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
<input type="button" value="Submit" onclick="submit()">
<script>
canvasElem.onmousemove = function(e) {
let ctx = canvasElem.getContext('2d');
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
};
async function submit() {
let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
let formData = new FormData();
formData.append("firstName", "John");
formData.append("image", imageBlob, "image.png");
let response = await fetch('/article/formdata/post/image-form', {
method: 'POST',
body: formData
});
let result = await response.json();
alert(result.message);
}
</script>
</body>
注意,这里是如何添加 Blob 文件的:
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)
这里有两点需要注意的是:
- 与 append 不同的是,set 方法在设置字段值之前,会将之前所有改字段名下的字段值全部删除,再替换为现在的新值。
- 发送文件,需要使用三个参数值语法,最后一个参数指定的是文件名。
其他方法还有:
- formData.delete(name)
- formData.get(name)
- formData.getAll(name)
- formData.has(name)
好了,就是这些了!
(完)