本文主要讲解,通过 web api 来处理各种参数问题,防止产生安全问题,以及更便利的操作。

先看一个示例:

const response = await fetch(url, {
method: 'POST',
body: `text=${text}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
const json = await response.json()

上述代码会出现一些“安全问题”,如通过 ​​text=${text}​​ 进行 SQL 或 HTML 注入。

开始之前,先罗列一下我们日常开发中经常用到的“内容类型 – Content-Type,用于指定资源的MIME类型 ​​media type​​ ,定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。

Content-Type 常用类型

说明

application/x-www-form-urlencoded

默认,表单数据

multipart/form-data

表单数据(可包含文件数据)

application/json

json 数据格式

image/png

png 图片格式

text/html

HTML格式

text/plain

纯文本格式

更多类型,可参考 MIME types 列表

encodeURIComponent

​<form>​​​表单请求默认格式 x-www-form-urlencoded,将表单内的数据转换为键值对,如 ​​title=%E4%BD%A0%E5%A5%BD&content=this+post+about+x-www-form-urlencoded​

Form 表单数据编码、解码--encodeURIComponent、URLSearchParams、FormData_form

<form action="" method="post" target="" enctype="application/x-www-form-urlencoded">
<p><label>文章标题:<input type="text" name="title" value="" /></label></p>
<p><label>文章内容:<textarea name="content" rows="5" cols="33"></textarea></label></p>
<p><button type="submit">提交</button></p>
</form>

注意: 由于历史的原因,表单使用的 Url 编码实现并不符合最新的标准,将空格处理成了 + 。

// href:https://example.com/?title=%E4%BD%A0%E5%A5%BD&content=this%20post%20about%20x-www-form-urlencoded new URL('https://example.com/?title=你好&content=this post about x-www-form-urlencoded')

MIME 类型的数据是 application/x-www-form-urlencoded 时,在 HTML 和 XForms 规范中定义仍然采用早期版本,​​用“+”代替“%20”替换空格​​​。-- ​​URL encoding the space character: + or %20?​

业务中,我们通常不是通过 action 的方式发送,而是通过 ajax/fetch 方式进行封装处理,此时需要对数据进行编码或解码操作。

// title=%E4%BD%A0%E5%A5%BD&content=this%20post%20about%20x-www-form-urlencoded
params = `title=${encodeURIComponent('你好')}&content=${encodeURIComponent('this post about x-www-form-urlencoded')}`

注意: 空格的处理结果 ​​encodeURIComponent(" ") // %20​

encodeURI:自身无法产生能适用于HTTP GET 或 POST 请求的URI,例如对于 XMLHTTPRequests,因为 “&”, “+”, 和 “=” 不会被编码,然而在 GET 和 POST 请求中它们是特殊字符

URLSearchParams

通过​​encodeURIComponent()​​​和​​decodeURIComponent()​​ 可以完成相关参数的编码、解码工作,但整体操作和处理都比较复杂,特别是在参数众多,需要获取指定参数的过程中。

function enhanceUrlArgs(query){
var args = {};
query.replace(/([^?&=]+)=([^&]+)/g, function(full, key, value){
args[key] = decodeURIComponent(value);
return "";
});
return args;
}

// {title: "你好", content: "this post about x-www-form-urlencoded"}
enhanceUrlArgs(new URL('https://example.com/?title=你好&content=this post about x-www-form-urlencoded').search)

可以通过 URLSearchParams 处理编码和解码 application/x-www-form-urlencoded 数据,处理方式大大简化。

示例:模拟上述 from 表达提交形式

const searchParams = new URLSearchParams()
searchParams.set('title', '你好')
searchParams.set('content', 'this post about x-www-form-urlencoded')

// title=%E4%BD%A0%E5%A5%BD&content=this+post+about+x-www-form-urlencoded
console.log(searchParams.toString())

注意:这个和 form 表单默认处理一致!

构造函数也可以接受“键/值对数组”

new URLSearchParams([
['title', '你好'],
['content', 'this post about x-www-form-urlencoded']
])

再者,也可以是“对象”

new URLSearchParams({
title: '你好',
content: 'this post about x-www-form-urlencoded'
})

还可以是“字符串”

new URLSearchParams('title=你好&content=this post about x-www-form-urlencoded') // location.search

读取方式

和设置方式一一对应

示例:获取上述表单数据

for (const [key, value] of searchParams) {
console.log(key, value)
}

得到“数组”

// [ ['title', '你好'], ['content', 'this post about x-www-form-urlencoded']]
[...searchParams]

得到“对象”

// {title: "你好", content: "this post about x-www-form-urlencoded"}
Object.fromEntries(searchParams)

Object.fromEntries(iterable) 方法把键值对列表转换为一个对象。

需要注意,对象的key是唯一的,可能出现有损转换

const searchParams2 = new URLSearchParams([
['category', 'javascript'],
['category', '前端']
])

// "category=javascript&category=%E5%89%8D%E7%AB%AF"
searchParams2.toString()
// {category: "前端"}
Object.fromEntries(searchParams2)

后者覆盖前者。对于表达 ​​from​​ 提交时,类似 select multiple 是真实存在的,需要格外注意。

避免有损转换:

Object.fromEntries(
[...new Set(searchParams2.keys())].map(key => [key, searchParams2.getAll(key)])
)

获取指定数据

方法

说明

​searchParams.entries()​

返回一个​​iterator​​可以遍历所有键/值对的对象。

​searchParams.get(key)​

获取指定搜索参数的第一个值

​searchParams.getAll(key)​

获取指定搜索参数的所有值,返回是一个数组

​searchParams.has(key)​

判断是否存在此搜索参数

​searchParams.keys()​

返回一个​​iterator​​包含了键/值对的所有键名

​searchParams.values()​

返回一个​​iterator​​包含了键/值对的所有值

示例改写

const response = await fetch(url, {
method: 'POST',
body: new URLSearchParams({ text })
})
const json = await response.json()

使用 URLSearchParams 作为 body,则 Content-Type 标头会自动设置为 application/x-www-form-urlencoded。

FormData

如果表单中包含文件怎么办?application/x-www-form-urlencoded 不支持文件,可以设置为 multipart/form-data 来支持。如果此时需要通过 ajax/fetch 发送请求,可以借助 FormData 进行封装数据。

FormData​​ 接口提供了一种表示表单数据的键值对 ​​key/value​​​ 的构造方式,并且可以轻松的将数据通过​​XMLHttpRequest.send()​​​ 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 ​​"multipart/form-data"​​,它会使用和表单一样的格式。

Form 表单数据编码、解码--encodeURIComponent、URLSearchParams、FormData_form_02

示例:模拟上述 from 表达提交形式

const formData = new FormData()
formData.set('title', '你好')
formData.set('content', 'this post about multipart-form-data')
formData.set('logo', document.forms[1].logo.files[0]) // document.forms[1].logo => fileInputElement

构造函数支持通过 form 表单元素,自动将form中的表单值也包含进去,包括文件内容也会被编码之后包含进去。

new FormData(document.forms[0])

读取方式

示例:获取上述表单数据

for (const [key, value] of formData) {
console.log(key, value)
}

其他方式暂时不支持,获取指定数据方式类似 **URLSearchParams **,且也提供了想对应的方法,可​​自行查阅​​。

改写示例

const formData = new FormData();
formData.set('text', text);

const response = await fetch(url, {
method: 'POST',
body: formData
})
const json = await response.json()

使用 FormData 作为 body,则 Content-Type 标头会自动设置为 multipart/form-data。

FormData 转换为 URLSearchParams

form 表单想通过 application/x-www-form-urlencoded 发送。

  1. 通过上述示例,直接在 form 中增加 action
  2. 进行转换
const formElement = document.querySelector('form')
const formData = new FormData(formElement)
const searchParams = new URLSearchParams(formData)

fetch(url, {
method: 'POST',
body: searchParams,
})

该方式,文件类型会被丢失。

其他类型

Blobs

fetch(url, {
method: 'POST',
body: blob
})

Content-Type 标头会自动设置为 ​​Blob.type​

Strings

fetch(url, {
method: 'POST',
body: JSON.stringify({ hello: 'world' }),
headers: { 'Content-Type': 'application/json' }
})

Buffers

fetch(url, {
method: 'POST',
body: new Uint8Array([]),
headers: { 'Content-Type': 'image/png' }
})

总结

如果不包含文件,且带有查询参数,可以使用 **URLSearchParams **;如果包含文件,需要使用 FormData