接收表单数据:serialize()函数解析
form-serialize可以快速收集表单数据,按照使用者意愿转化为对象或字符串输出,以便于提交至服务器。form-serialize不是浏览器自带的JS方法,而是第三方工具库。
一、form-serialize作用与引入
作用:
form-serialize可以快速收集表单数据,按照使用者意愿转化为对象或字符串输出,以便于提交至服务器。
引入:
form-serialize不是浏览器自带的JS方法,而是第三方工具库。可以直接通过scrip标签引入:
<script src="https://cdn.jsdelivr.net/npm/form-serialize@0.7.2/form-serialize.min.js"></script>
或者到npm网站搜索form-serialize(),将代码文件保存到本地引入,也可以在终端安装后获取:
npm install form-serialize
import serialize from 'form-serialize';
const data = serialize(form, { hash: true, empty: true });
二、输出
serialize的使用方法为:
const form=document.querySelector('form')
/*
假设表单内容为:
<form action="">
<input type="text" name="username" id="" value="user">
<input type="text" name="age" id="" value="18">
<input type="text" name="infor" id="" value="">
</form>
*/
//1.转化为query string参数
const data=serialize(form)
//2.转化为没有空内容的对象
const dataobj=serialize(form,hash:true)
//3.转化为有空内容的对象
const dataobjWithEempty=serialize(form,{hash:true,empty:true})
- 方法1传出为:
?username=user&age=18
这个形式的字符串可以直接作为AJAX请求的body,或URL的query string。 - 方法2传出为:
dataobj={
username:'user',
age:18
}
- 方法3传出为:
dataobj={
username:'user',
age:18,
infor:''
}
发现区别了吗?方法一传出query string字符串,方法二传出不含空白字符串的表单数据对象,方法三会将收集到的所有信息传出为对象。其中:
hash
在代表哈希表,是一种键值对应的对象,所以{hash:true}就代表传出对象。- 而
empty:true
则代表传出对象中包含空白字符串,接下来我们观察底层代码如何实现。
从npm下载的完整js代码放在文末
三、解析
serialize函数定义:
function serialize(form, options) {
if (typeof options != 'object') {
options = { hash: !!options };
}
else if (options.hash === undefined) {
options.hash = true;
}
var result = (options.hash) ? {} : '';
var serializer = options.serializer || ((options.hash) ? hash_serializer : str_serialize);
var elements = form && form.elements ? form.elements : [];
//Object store each radio and set if it's empty or not
var radio_store = Object.create(null);
for (var i=0 ; i<elements.length ; ++i) {
var element = elements[i];
// ingore disabled fields
if ((!options.disabled && element.disabled) || !element.name) {
continue;
}
// ignore anyhting that is not considered a success field
if (!k_r_success_contrls.test(element.nodeName) ||
k_r_submitter.test(element.type)) {
continue;
}
var key = element.name;
var val = element.value;
// we can't just use element.value for checkboxes cause some browsers lie to us
// they say "on" for value when the box isn't checked
if ((element.type === 'checkbox' || element.type === 'radio') && !element.checked) {
val = undefined;
}
// If we want empty elements
if (options.empty) {
// for checkbox
if (element.type === 'checkbox' && !element.checked) {
val = '';
}
// for radio
if (element.type === 'radio') {
if (!radio_store[element.name] && !element.checked) {
radio_store[element.name] = false;
}
else if (element.checked) {
radio_store[element.name] = true;
}
}
// if options empty is true, continue only if its radio
if (val == undefined && element.type == 'radio') {
continue;
}
}
else {
// value-less fields are ignored unless options.empty is true
if (!val) {
continue;
}
}
// multi select boxes
if (element.type === 'select-multiple') {
val = [];
var selectOptions = element.options;
var isSelectedOptions = false;
for (var j=0 ; j<selectOptions.length ; ++j) {
var option = selectOptions[j];
var allowedEmpty = options.empty && !option.value;
var hasValue = (option.value || allowedEmpty);
if (option.selected && hasValue) {
isSelectedOptions = true;
// If using a hash serializer be sure to add the
// correct notation for an array in the multi-select
// context. Here the name attribute on the select element
// might be missing the trailing bracket pair. Both names
// "foo" and "foo[]" should be arrays.
if (options.hash && key.slice(key.length - 2) !== '[]') {
result = serializer(result, key + '[]', option.value);
}
else {
result = serializer(result, key, option.value);
}
}
}
// Serialize if no selected options and options.empty is true
if (!isSelectedOptions && options.empty) {
result = serializer(result, key, '');
}
continue;
}
result = serializer(result, key, val);
}
// Check for all empty radio buttons and serialize them with key=""
if (options.empty) {
for (var key in radio_store) {
if (!radio_store[key]) {
result = serializer(result, key, '');
}
}
}
return result;
}
我们可以发现,要求传入参数为:(form,options),form为我们获取的表单对象,options可以省略。
首先,程序查询传入参数:
if (typeof options != 'object') {
options = { hash: !!options };
}
else if (options.hash === undefined) {
options.hash = true;
}
如果options不是对象,就使用!!
强行将options转化为bool值,存储到hash属性中,并将这对键值转化为对象。
这允许我们在使用serialize转化对象时简写:const dataobj=serialize(form,true)
完成了简写判断,进行options为对象时的判断:
//result为传出参数
//hash为:真-result初始化为对象;假-初始化为字符串
var result = (options.hash) ? {} : '';
//options.serializer存在,直接使用,若不存在:
//hash为true,调用hash_serializer
//hash为false,调用str_serialize
var serializer = options.serializer || ((options.hash) ? hash_serializer : str_serialize);
其中:
- hash_serializer:自定义函数,将表单数据序列化为 JavaScript 对象。
- str_serialize:自定义函数,将表单数据序列化为 URL 编码的字符串。
接下来,接收radio类型单选按钮数据,创建空对象:
//将表单格式化为数组,进行遍历
var elements = form && form.elements ? form.elements : [];
//创建空对象
var radio_store = Object.create(null);
之后进入遍历elments的for循环。
在下面这段代码中:
- 过滤掉不需要序列化的字段:
- 禁用的字段(除非 options.disabled 为 true)。
- 没有 name 属性的字段。
- 不属于“成功控件(表单要收集的数据)”的字段。
if ((!options.disabled && element.disabled) || !element.name) {
continue;
}
// ignore anyhting that is not considered a success field
if (!k_r_success_contrls.test(element.nodeName) ||
k_r_submitter.test(element.type)) {
continue;
}
在下面这段代码中:
- 设置表单的键与值
- 检查复选框与单选框是否被选中,若没有,标记为undefined,表示不该被记录。
var key = element.name;
var val = element.value;
if ((element.type === 'checkbox' || element.type === 'radio') && !element.checked) {
val = undefined;
}
获取属性:
// If we want empty elements-如果options.empty为true,即我们需要空白字符串值
if (options.empty) {
// for checkbox-获取复选框值
if (element.type === 'checkbox' && !element.checked) {
val = '';
}
// for radio-获取单选框值
if (element.type === 'radio') {
if (!radio_store[element.name] && !element.checked) {
radio_store[element.name] = false;
}
else if (element.checked) {
radio_store[element.name] = true;
}
}
// if options empty is true, continue only if its radio
//如果控件无有效值(未被选择)且为单选框,直接进行下次迭代
if (val == undefined && element.type == 'radio') {
continue;
}
}
else {
//不需要空字符串的情况,没有值直接跳过
// value-less fields are ignored unless options.empty is true
if (!val) {
continue;
}
}
接下来进行遍历中的多选下拉选项获取(单选下拉可以直接通过下拉对象的value属性获取)
if (element.type === 'select-multiple') {
//初始化值数组
val = [];
//获取选项数组,用于遍历
var selectOptions = element.options;
//每次迭代初始化未被选择bool类型
var isSelectedOptions = false;
for (var j=0 ; j<selectOptions.length ; ++j) {
var option = selectOptions[j];
//该选项是否被允许空值
var allowedEmpty = options.empty && !option.value;
//当前选项是否有有效值:有真值或被允许空值
var hasValue = (option.value || allowedEmpty);
//是否被选择,是否有有效值
if (option.selected && hasValue) {
//给bool值赋值:被选中
isSelectedOptions = true;
// 处理格式
/*
如果用户配置了 options.hash 为 true,表示需要将结果序列化为对象格式。
key.slice(key.length - 2) !== '[]':
检查当前选项的 name 属性是否以 [] 结尾。
如果没有以 [] 结尾,则在键名后面追加 [],以确保多选下拉框的值被正确处理为数组。
递归调用 serializer:
调用序列化函数(hash_serializer 或 str_serialize),将当前选项的键值对添加到结果中:
如果是对象序列化(hash_serializer),会将值存储为数组。
如果是字符串序列化(str_serialize),会将值编码为 URL 查询字符串格式。
*/
if (options.hash && key.slice(key.length - 2) !== '[]') {
result = serializer(result, key + '[]', option.value);
}
else {
result = serializer(result, key, option.value);
}
}
}
// Serialize if no selected options and options.empty is true
if (!isSelectedOptions && options.empty) {
result = serializer(result, key, '');
}
continue;
}
这是获取复选下拉的部分,接下来:
递归调用,将当前表单键值对序列化添加到结果中,以便于处理正确的嵌套逻辑。
result = serializer(result, key, val);
检查所有的值都不为空/未定义
//如果允许空字符串
if (options.empty) {
for (var key in radio_store) {
if (!radio_store[key]) {
result = serializer(result, key, '');
}
}
}
//若未允许空字符串,在前面值为空的对象会直接跳过进入下次迭代
返回结果:
return result;
四、form-serialize.js文件完整代码
// get successful control from form and assemble into object
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2
// types which indicate a submit action and are not successful controls
// these will be ignored
var k_r_submitter = /^(?:submit|button|image|reset|file)$/i;
// node names which could be successful controls
var k_r_success_contrls = /^(?:input|select|textarea|keygen)/i;
// Matches bracket notation.
var brackets = /(\[[^\[\]]*\])/g;
// serializes form fields
// @param form MUST be an HTMLForm element
// @param options is an optional argument to configure the serialization. Default output
// with no options specified is a url encoded string
// - hash: [true | false] Configure the output type. If true, the output will
// be a js object.
// - serializer: [function] Optional serializer function to override the default one.
// The function takes 3 arguments (result, key, value) and should return new result
// hash and url encoded str serializers are provided with this module
// - disabled: [true | false]. If true serialize disabled fields.
// - empty: [true | false]. If true serialize empty fields
function serialize(form, options) {
if (typeof options != 'object') {
options = { hash: !!options };
}
else if (options.hash === undefined) {
options.hash = true;
}
//真:初始化为对象,假:初始化为字符串
var result = (options.hash) ? {} : '';
var serializer = options.serializer || ((options.hash) ? hash_serializer : str_serialize);
var elements = form && form.elements ? form.elements : [];
//Object store each radio and set if it's empty or not
var radio_store = Object.create(null);
for (var i=0 ; i<elements.length ; ++i) {
var element = elements[i];
// ingore disabled fields
if ((!options.disabled && element.disabled) || !element.name) {
continue;
}
// ignore anyhting that is not considered a success field
if (!k_r_success_contrls.test(element.nodeName) ||
k_r_submitter.test(element.type)) {
continue;
}
var key = element.name;
var val = element.value;
// we can't just use element.value for checkboxes cause some browsers lie to us
// they say "on" for value when the box isn't checked
if ((element.type === 'checkbox' || element.type === 'radio') && !element.checked) {
val = undefined;
}
// If we want empty elements
if (options.empty) {
// for checkbox
if (element.type === 'checkbox' && !element.checked) {
val = '';
}
// for radio
if (element.type === 'radio') {
if (!radio_store[element.name] && !element.checked) {
radio_store[element.name] = false;
}
else if (element.checked) {
radio_store[element.name] = true;
}
}
// if options empty is true, continue only if its radio
if (val == undefined && element.type == 'radio') {
continue;
}
}
else {
// value-less fields are ignored unless options.empty is true
if (!val) {
continue;
}
}
// multi select boxes
if (element.type === 'select-multiple') {
val = [];
var selectOptions = element.options;
var isSelectedOptions = false;
for (var j=0 ; j<selectOptions.length ; ++j) {
var option = selectOptions[j];
var allowedEmpty = options.empty && !option.value;
var hasValue = (option.value || allowedEmpty);
if (option.selected && hasValue) {
isSelectedOptions = true;
// If using a hash serializer be sure to add the
// correct notation for an array in the multi-select
// context. Here the name attribute on the select element
// might be missing the trailing bracket pair. Both names
// "foo" and "foo[]" should be arrays.
if (options.hash && key.slice(key.length - 2) !== '[]') {
result = serializer(result, key + '[]', option.value);
}
else {
result = serializer(result, key, option.value);
}
}
}
// Serialize if no selected options and options.empty is true
if (!isSelectedOptions && options.empty) {
result = serializer(result, key, '');
}
continue;
}
result = serializer(result, key, val);
}
// Check for all empty radio buttons and serialize them with key=""
if (options.empty) {
for (var key in radio_store) {
if (!radio_store[key]) {
result = serializer(result, key, '');
}
}
}
return result;
}
function parse_keys(string) {
var keys = [];
var prefix = /^([^\[\]]*)/;
var children = new RegExp(brackets);
var match = prefix.exec(string);
if (match[1]) {
keys.push(match[1]);
}
while ((match = children.exec(string)) !== null) {
keys.push(match[1]);
}
return keys;
}
function hash_assign(result, keys, value) {
if (keys.length === 0) {
result = value;
return result;
}
var key = keys.shift();
var between = key.match(/^\[(.+?)\]$/);
if (key === '[]') {
result = result || [];
if (Array.isArray(result)) {
result.push(hash_assign(null, keys, value));
}
else {
// This might be the result of bad name attributes like "[][foo]",
// in this case the original `result` object will already be
// assigned to an object literal. Rather than coerce the object to
// an array, or cause an exception the attribute "_values" is
// assigned as an array.
result._values = result._values || [];
result._values.push(hash_assign(null, keys, value));
}
return result;
}
// Key is an attribute name and can be assigned directly.
if (!between) {
result[key] = hash_assign(result[key], keys, value);
}
else {
var string = between[1];
// +var converts the variable into a number
// better than parseInt because it doesn't truncate away trailing
// letters and actually fails if whole thing is not a number
var index = +string;
// If the characters between the brackets is not a number it is an
// attribute name and can be assigned directly.
if (isNaN(index)) {
result = result || {};
result[string] = hash_assign(result[string], keys, value);
}
else {
result = result || [];
result[index] = hash_assign(result[index], keys, value);
}
}
return result;
}
// Object/hash encoding serializer.
function hash_serializer(result, key, value) {
var matches = key.match(brackets);
// Has brackets? Use the recursive assignment function to walk the keys,
// construct any missing objects in the result tree and make the assignment
// at the end of the chain.
if (matches) {
var keys = parse_keys(key);
hash_assign(result, keys, value);
}
else {
// Non bracket notation can make assignments directly.
var existing = result[key];
// If the value has been assigned already (for instance when a radio and
// a checkbox have the same name attribute) convert the previous value
// into an array before pushing into it.
//
// NOTE: If this requirement were removed all hash creation and
// assignment could go through `hash_assign`.
if (existing) {
if (!Array.isArray(existing)) {
result[key] = [ existing ];
}
result[key].push(value);
}
else {
result[key] = value;
}
}
return result;
}
// urlform encoding serializer
function str_serialize(result, key, value) {
// encode newlines as \r\n cause the html spec says so
value = value.replace(/(\r)?\n/g, '\r\n');
value = encodeURIComponent(value);
// spaces should be '+' rather than '%20'.
value = value.replace(/%20/g, '+');
return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + value;
}
module.exports = serialize;
五、原库获取地址

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)