作者:valentinogagliardi
譯者:前端小智
來源:github
阿裡雲伺服器很便宜火爆,今年比去年便宜,10.24~11.11購買是1年86元,3年229元,可以點選 閱讀原文 進行參與。
為了保證的可讀性,本文采用意譯而非直譯。
重新介紹 HTML 表單
網頁不僅僅是用來顯示資料的。有了 HTML 表單,咱們可以收集和操作使用者資料。在本章中,通過建構一個簡單的 HTML 表單來學習表單的相關的知識。
在這個過程中,會了解更多關于 DOM 事件的資訊,從在
第8章
我們知道了一個
元素是一個 HTML 元素,它可能包含其他的子元素,比如:
- 用于捕獲資料
- 用于捕獲文本
- 用于送出表單
在本章中,咱們建構一個包含
、
和
的表彰。理想情況下,每個
input
都應該具有
type
的屬性,該屬性訓示輸入類型: 例如
text
、
email
、
number
、
date
等。除了
type
屬性之外,可能還希望向每個表單元素添加
id
屬性。
input
和
textarea
也可以有一個
name
屬性。如果你們想在不使用 JS 的情況下發送表單,name 屬性非常重要。稍後會詳細介紹。
另外,将每個表單元素與
關聯也是一種常見的方式。在下面的示例中,會看到每個
label
與
for
屬性綁定對應
input
元素的
id
,作用是點選
label
元素就能讓
input
聚焦。
如果沒有填寫所有需要的資訊,使用者将無法送出表單。這是一個避免空資料的簡單驗證,進而防止使用者跳過重要字段。有了這些知識,現在就可以建立 HTML 表單了。建立一個名為 form.html 的新檔案并建構 HTML:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SZlNGZ5kTN0kzN2ETM4gzNyAjZ3kTOhRjNmlDNkRGO08CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
如上所述,表單中的
input
具有正确的屬性,從現在開始,可以通過填充一些資料來測試表單。編寫 HTML 表單時,要特别注意
type
屬性,因為它決定了使用者能夠輸入什麼樣的資料。
HTML5 還引入了表單驗證:例如,類型為
email
的輸入隻接受帶有
“at”
符 号
@
的電子郵件位址。不幸的是,這是對電子郵件位址應用的惟一檢查:沒有人會阻止使用者輸入類似
[email protected]
這樣的電子郵件。它有
@
,但仍然是無效的(用于電子郵件輸入的
pattern
屬性可以幫助解決這個問題。
在
上有很多可用的屬性,我發現
minlength
和
maxlength
是最有用的兩個。在實戰中,它們可以阻止懶惰的垃圾郵件發送者發送帶有
“aa”
或
“testtest”
的表單。
有了這個表單,咱們就可以更進一步了,接着,來看下表單是如何工作的。
表單是如何工作
HTML 表單是 HTMLFormElement 類型的一個元素。與幾乎所有的 HTML 元素一樣,它連接配接到
HTMLElement
,後者又連接配接到
EventTarget
。當我們通路 DOM 元素時,它們被表示為 JS 對象。在浏覽器中試試這個:
const aForm =
輸出是
“object”
,而像
HTMLElement
或
EventTarget
這樣的實體是函數:
console.log(
是以,如果任何 HTML 元素都連接配接到
EventTarget
,這意味着
是
EventTarget
的“執行個體”,如下:
const aForm =
form
是
EventTarget
的一種專門化類型。每個
EventTarget
都可以接收和響應 DOM 事件(如第8章所示)。
DOM 事件有很多類型,比如
click
、
blur
、
change
等等。現在,咱們感興趣的是 HTML 表單特有的
submit
事件。當使用者單擊
input
或
type
為 “submit” 的按鈕(元素必須出現在表單中)時,就會分派
submit
事件,如下所示:
請注意,
Submit
就在表單内部。一些開發人員使用
input
方式:
隻要表單存在上面 列出的任何一種按鈕,那麼在相應表單控件擁有焦點的情況下,按Enter鍵就可以送出表單。(textarea 是一個例外,在文本中回車會換行。)如果表單裡沒有送出按鈕,按Enter鍵不會送出表單。
咱們的目标是擷取表單上的所有使用者輸入,是以,需要監聽
submit
事件。
const formSelector =
DOM 還提供
document.forms
,這是頁面内所有表單的集合。咱們現在隻需要:
const formSelector =
現在的想法是:給定一個表單選擇器,我們可以注冊一個事件監聽器來響應表單的發送。為了注冊監聽器,我們可以使用構造函數,并讓它調用一個名為
init
的方法。在與 form.html 相同的檔案夾中建立一個名為 form.js 的新檔案,并從一個簡單的類開始:
咱們的事件監聽器是
this.handleSubmit
,與每個事件監聽器一樣,它可以通路名為
event
的參數。從第8章中應該知道,事件是實際分派的事件,其中包含有關動作本身的許多有用資訊。咱們來實作
this.handleSubmit
:
然後,執行個體化類
From
:
const formSelector =
此時,在浏覽器中打開 form.html。輸入内容并點選“送出”。會發生什麼呢? 輸出如下:
//localhost:63342/little-javascript/code/ch10/form.html?name=Valentino&description=Trip+to+Spoleto&tak=We%27re+going+to+visit+the+city%21
這是怎麼回事?大多數 DOM 事件都有所謂的“預設行為”。
submit
事件尤其嘗試将表單資料發送到虛構的伺服器。這就是在沒有 JS的 情況下發送表單的方式,因為它是基于 Django、Rails和friends 等 web 架構的應用程式的一部分。
每個輸入值都映射到相應的
name
屬性。在本例中不需要
name
,因為這裡咱們想用 JS 來控制表單,是以需要禁用預設行為。可以通過調用
preventDefault
來禁用:
儲存檔案,然後再次重新整理 form.html。嘗試填寫表格,然後單擊送出。會看到
event
對象列印到控制台:
在
event
對象的許多屬性中,還有
event.target
,這表明咱們的 HTML 表單與所有輸入一起儲存在那裡,來看看是否确實如此。
從 from 提取資料
為了擷取表單的值,通過檢查
event.target
,您将發現有一個名為
elements
的屬性。該屬性是表單中所有元素的集合。這個
elements
集合是一個有序清單,其中包含着表單的所有字段,例如
、
、
和
。如果嘗試使用
console.log(event.target.elements)
進行列印,則會看到:
0: input#name
每個表單字段在
elements
集合中的順序,與它們出現在标記中的順序相同,可以按照位置和
name
特性來通路它們。現在,咱們有兩種方法擷取輸入的值:
- 通過類似數組的表示法:
event.target.elements[0].value
- 通過 id:
event.target.elements.some_id.value
實際上,如果現在希望在每個表單元素上添加适當的
id
屬性,則可以通路與
event.target.elements.some_id
相同的元素,其中
id
是你配置設定給該屬性的字元串。由于
event.target.elements
首先是一個對象,是以還可以使用 ES6 對象解構:
const { name, description, task } =
這種做法不是
100%
推薦的,例如在 TypeScript 你會得到一個錯誤,但隻要寫
“vanilla JS”
就可以了。現在有了這些值,咱們就可以完成
handleSubmit
了,在此過程中,還建立了另一個名為
saveData
的方法。現在它隻是将值列印到控制台:
這種儲存資料的方式并不是最好的判斷。如果字段更改怎麼辦?現在咱們有了
name
,
task
和
description
,但将來可能會添加更多輸入,是以需要動态提取這些字段。當然,還要解決對象銷毀問題,來看看
event.target.elements
0: input#name
它看起來像一個數組。咱們使用
map
方法将其轉換為僅包含
name
,
description
和
task
(過濾按鈕類型 submit):
在浏覽器中嘗試一下并檢視控制台:
Uncaught
“ .map不是函數”
。那麼
event.target.elements
到底是什麼?看起來像一個數組,但卻是另一種野獸:它是
HTMLFormControlsCollection
。在 第8章中,咱們對這些内容有所了解,并看到一些 DOM 方法傳回了
HTMLCollection
。
// Returns an HTMLCollection
HTML 集合看起來類似于數組,但是它們缺少諸如
map
或
filter
之類的用于疊代其元素的方法。仍然可以使用方括号表示法通路每個元素,我們可以通過 Array.from 将類似數組轉成真正的數組:
通過
Array.from
方法将
event.target.elements
構造一個數組。
Array.from
接受一個映射函數作為第二個參數,進一步優化:
重新整理 form.html,填寫表單,然後按“送出”。在控制台中看到以下數組:
"Valentino", "We're going to visit the city!", undefined]
最後,我想生成一個對象數組,其中每個對象還具有相關表單輸入的name屬性:
再次重新整理 form.html,填寫表單,将看到:
good job,有一個
undefined
的空值,它來自
button
元素。
map
的預設行為是在“空”值的情況下傳回
undefined
。由于我們檢查了
if (formInput.type !== "submit")
,是以
button
元素未從
map
傳回,而是被
undefined
取代。我們可以稍後将其删除,現在來看看
localStorage
。
了解 localStorage 并完善類
咱們有時候需要為使用者保留一些資料,這樣做有很多原因。例如考慮一個筆記應用程式,使用者可以在 HTML表單中插入新内容,然後再回來檢視這些筆記。下次她打開頁面時,将在其中找到所有内容。
在浏覽器中儲存資料有哪些選項? 持久化資料的一種重要方法是使用資料庫,但這裡我們隻有一些 HTML、JS 和浏覽器。然而,在現代浏覽器中有一個内置的工具,它就像一個非常簡單的資料庫,非常适合我們的需要:
localStorage
。
localStorage
的行為類似于 JS 對象,它有一堆方法:
-
用于儲存資料setItem
-
用于讀取資料getItem
-
用于删除所有值clear
- removeItem 用于清除對應的
的值key
稍後我們将看到
setItem
和
getItem
,首先咱們先得有一個 form.html 檔案,内容如下:
還有用于攔截送出事件的相關 JS 代碼:
此時,咱們需要實作
this.saveData
來将每個筆記儲存到
localStorage
。這樣做時,需要保持盡可能的通用。換句話說,我不想用直接儲存到
localStorage
的邏輯來填充
this.saveData
。
相反,咱們為
Form
類提供一個外部依賴項(另一個類),該類的作用是實作實際代碼。将來我們将這些筆記資訊儲存到
localStorage
還是資料庫中都沒有關系。對于每種用例,我們應該能夠為
Form
提供不同的“存儲”,并随着需求的變化而從一種轉換為另一種。為此,我們首先調整構造函數以接受新的“存儲”參數:
現在,随着類的複雜度增加,需要驗證構造函數的參數。作為一個用于處理 HTML 表單的類,咱們至少需要檢查
formSelector
是否是 form 類型的 HTML 元素:
constructor(formSelector, storage) {
// Validating the arguments
if (!(formSelector instanceof HTMLFormElement))
throw Error(`Expected a form element got ${formSelector}`);
如果
formSelector
不是一個表單類型的,就會報錯。另外還要驗證
storage
,因為我們必須将使用者輸入存儲到某個地方。
存儲實作将是另一個類。在我們的例子中,可以是類似于通用
LocalStorage
的東西,在 form.js 中建立類
LocalStorage
:
現在,有了這個結構,我們就可以連接配接
Form
和
LocalStorage
:
-
中的Form
應該調用saveData
實作Storage
-
和LocalStorage.save
可以是靜态的LocalStorage.get
仍然在 form.js 中,如下更改類方法:
代碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
原文:https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter10.md
交流
延伸閱讀
通過 41 個 問答方式快速了解學習 Git
重溫一下 JS 進階需要掌握的 13 個概念
13 個 JS 數組精簡技巧,一起來看看。
【JS 口袋書】第 10 章:使用異步 JavaScript