一、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(),將代碼文件保存到本地引入,也可以在終端安裝后直接使用require獲取:
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傳出為:
www.example.com?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 notvar radio_store = Object.create(null);for (var i=0 ; i<elements.length ; ++i) {var element = elements[i];// ingore disabled fieldsif ((!options.disabled && element.disabled) || !element.name) {continue;}// ignore anyhting that is not considered a success fieldif (!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 checkedif ((element.type === 'checkbox' || element.type === 'radio') && !element.checked) {val = undefined;}// If we want empty elementsif (options.empty) {// for checkboxif (element.type === 'checkbox' && !element.checked) {val = '';}// for radioif (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 radioif (val == undefined && element.type == 'radio') {continue;}}else {// value-less fields are ignored unless options.empty is trueif (!val) {continue;}}// multi select boxesif (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 trueif (!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_serializevar 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 fieldif (!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 trueif (!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 trueif (!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 notvar radio_store = Object.create(null);for (var i=0 ; i<elements.length ; ++i) {var element = elements[i];// ingore disabled fieldsif ((!options.disabled && element.disabled) || !element.name) {continue;}// ignore anyhting that is not considered a success fieldif (!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 checkedif ((element.type === 'checkbox' || element.type === 'radio') && !element.checked) {val = undefined;}// If we want empty elementsif (options.empty) {// for checkboxif (element.type === 'checkbox' && !element.checked) {val = '';}// for radioif (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 radioif (val == undefined && element.type == 'radio') {continue;}}else {// value-less fields are ignored unless options.empty is trueif (!val) {continue;}}// multi select boxesif (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 trueif (!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 numbervar 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 sovalue = 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;
五、原庫獲取地址
form-serialize - npm