toFrankie / blog

种一棵树,最好的时间是十年前。其次,是现在。
20 stars 1 forks source link

六、Ajax 之 FormData #215

Open toFrankie opened 1 year ago

toFrankie commented 1 year ago

表单编码

当用户提交表单时,表单中的数据(每个表单元素的名字和值)编码到一个字符串并随请求发送。默认情况下,HTML 表单通过 POST 方法发送给服务器,而编码后的表单数据则用做请求主体。

对表单数据使用的编码方案相对简单:对每个表单元素的名字和值执行普通的 URL 编码(使用十六进制转义码替换特殊字符),使用 = 把编码后的名字和值分开,并使用 & 符号分开 名/值对

一个简单的表单的编码如下所示:

name=Frankie&age=20&height=180

表单数据编码格式有一个正式的 MIME 类型:

application/x-www-form-urlencoded

当使用 POST 方法提交这种顺序的表单数据市,必须将请求头 Content-Type 设置为该值。 注意:这种类型的编码并不需要 HTML 表单,在 Ajax 应用中,希望发送给服务器的很可能是一个 JavaScript 对象,前面展示的数据变成 JavaScript 对象的表单编码形式可能是:

{ name: 'Frankie', age: 20, height: 180 }

表单编码在 Web 上如此广泛地使用,同时所有服务器端的编程语言都能得到良好的支持,所以非表单数据的表单编码通常也是容易实现的事情,下面代码展示如何实现对象属性的表单编码:

function encodeFormData(data) {
  if (!data) return ''
  const pairs = []    // 用于保存名值对
  for (let key in data) {
    if (!data.hasOwnProperty(key) || typeof data[key] === 'function') {
      continue
    }
    let value = data[key].toString()                        // 把值转换成字符串
    let name = encodeURIComponent(key.replace('%20', '+'))  // 编码名字
    value = encodeURIComponent(value.replace('%20', '+'))   // 编码值
    pairs.push(name + '=' + value)
  }
  return pairs.join('&')  // 返回使用'&'连接的名值对
}

const data = { name: '越前君', age: 20, height: 180 }
console.log(encodeFormData(data))   // name=%E8%B6%8A%E5%89%8D%E5%90%9B&age=20&height=180

表单序列化

随着 Ajax 的出现,表单序列化已经成为一种常见的需求,在 JavaScript 中,可以利用表单字段的 type 属性,连同 namevalue 属性一起实现对表单序列化。在编写代码之前,有必要先弄清楚在表单提交期间,浏览器是如何将数据发送给服务器的。

在表单序列化过程中,一般不包括任何按钮字段,因为结果字符串很可能是通过其他方式提交的。除此之外的其他上述规则都应该遵循。

在下面表单序列化 serialize() 函数中,首先定义了一个名为 parts 数组,用于保存将要创建的字符串的各个部分。然后,通过 for 循环遍历每个表单字段,并将其保存在 field 变量中。在活动一个字段的引用之后,使用 switch 语句检测其 type 属性。

序列化过程中最麻烦的就是 <select> 元素,它可能是单选框,也可能是多选框。为此,需要遍历控件中的每一个选项,并在相应选项被选中的情况下向数组添加一个值。对于单选框,只有一个选中项,而多选框则可能有零个或多个选中项。这里的代码适用于这两种选择框,至于可选项的数量则是由浏览器控制的。在找到一个选中项之后,需要确定使用什么值。如果不存在 value 属性,或者虽然存在该属性,但值为空字符串,都要使用选项的文本来代替。为检查这个特性,在 DOM 兼容的浏览器中需要使用 hasAttribute() 方法,而在 IE7 中需要使用属性 specified 属性。

如果表单中包含 <fieldset> 元素,则该元素会出现在元素集合中,但没有 type 属性。因此,如果 type 属性未定义,则不需要对其进行序列化。同样,对于各种按钮以及文件输入字段也是如此(文件输入字段在表单提交过程中包含文件的内容;但是,这个字段是无法模仿的,序列化时一般都要忽略)

对于单选按钮和复选框,要检查其 checked 属性是否被设置为 false,如果是则退出 switch 语句。如果 checked 属性是 true,则继续执行 default 语句,即将当前字段的名称和值进行编码,然后添加到 parts 数组中。函数的最后一步,就是使用 join() 个数整个字符串,也就是用 & 来分隔每一个表单字段。

最后 serialize() 函数会以查询字符串的格式输出序列化之后的字符串。

// 未完待续,例子晚点加上