--- title: 表单数据校验 slug: Learn/Forms/Form_validation tags: - HTML translation_of: Learn/Forms/Form_validation original_slug: Learn/HTML/Forms/Data_form_validation ---
{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/Sending_and_retrieving_form_data", "Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms")}}
表单校验帮助我们确保用户以正确格式填写表单数据,确保提交的数据能使我们的应用程序正常工作。本文将告诉您需要了解的有关表单校验的内容。
预备知识: | 计算机基础能力,对 HTML、CSS 和 JavaScript 有一定的理解。 |
---|---|
目标: | 理解表单校验是什么,为什么它很重要,以及如何实现它。 |
访问任何一个带注册表单的网站,你都会发现,当你提交了没有输入符合预期格式的信息的表单时,注册页面都会给你一个反馈,这些信息可能看起来像下面这样的:
这就是表单校验 —— 当你向 Web 应用输入数据时,应用会验证你输入的数据是否是正确的。如果验证通过,应用允许提交这些数据到服务器并储存到数据库中(通常情况下),如果验证未通过,则 Web 应用会提示你有错误的数据,并且一般都会明确的告诉你错误发生在哪里。表单校验可以通过许多不同的方式实现。
译者注:下面一段在英文原文中已经删除
(事实上,没有人愿意填写表单 —— 很多证据表明,用户对填写表单这件事情都感到很烦恼,如果他们在填写表单的过程中遇到一些自己无法理解的问题,通常都会导致他们直接离开你的 Web 应用,简而言之,表单是一个很烦人的东西。)
我们希望把填写表单变的越简单越好。那么,为什么我们还坚持进行表单的数据校验呢?这有三个最主要的原因:
警告: 永远不要相信从客户端传递到服务器的数据。 即使您的表单正确验证并防止输入格式错误,恶意用户仍然可以更改网络请求。
在 Web 中,你可能会遇见各种不同的表单校验:
在真实的项目开发过程中,开发者一般都倾向于使用客户端校验与服务器端校验的组合校验方式以更好的保证数据的正确性与安全性。
HTML5 一个特别有用的新功能就是,可以在不写一行脚本代码的情况下,即对用户的输入进行数据校验,这都是通过表单元素的校验属性实现的,这些属性可以让你定义一些规则,用于限定用户的输入,比如某个输入框是否必须输入,或者某个输入框的字符串的最小最大长度限制,或者某个输入框必须输入一个数字、邮箱地址等;还有数据必须匹配的模式。如果表单中输入的数据都符合这些限定规则,那么表示这个表单校验通过,否则则认为校验未通过。
当一个元素校验通过时:
如果一个元素未校验通过:
在这一节,我们将会看到一些用于{{HTMLElement("input")}}元素校验的HTML5的特性。
让我们用一个简单的例子开始 — 一个可以让你从香蕉或樱桃中选择你最喜欢的水果的input。 这个包含了一个简单的文本{{HTMLElement("input")}} 和一个与之匹配的label,还有一个 submit {{htmlelement("button")}}。你可以在GitHub fruit-start.html找到源码,在线例子如下:
<form> <label for="choose">Would you prefer a banana or cherry?</label> <input id="choose" name="i_like"> <button>Submit</button> </form>
input:invalid { border: 2px dashed red; } input:valid { border: 2px solid black; }
{{EmbedLiveSample("Hidden_code", "100%", 50)}}
开始之前,先拷贝一份 fruit-start.html
放在你硬盘上的新目录里。
最简单的HTML5校验功能是 {{htmlattrxref("required", "input")}}属性 — 如果要使输入成为必需的,则可以使用此属性标记元素。 当设置此属性时,如果输入为空,该表单将不会提交(并将显示错误消息),输入也将被视为无效。
添加一个 required
属性到你的 input 元素, 如下所示:
<form> <label for="choose">Would you prefer a banana or cherry?</label> <input id="choose" name="i_like" required> <button>Submit</button> </form>
同时注意在示例文件中包含的 CSS :
input:invalid { border: 2px dashed red; } input:valid { border: 2px solid black; }
以上样式效果为:在校验失败时 输入框会有一个亮红色的虚线边框, 在校验通过时会有一个更微妙的黑色边框。在以下示例中尝试新的行为:
{{EmbedLiveSample("required_属性", "100%", 50)}}
另一个常用的校验功能是 {{htmlattrxref("pattern","input")}} 属性, 以 Regular Expression 作为 value 值. 正则表达式 (regex) 是一个可以用来匹配文本字符串中字符的组合的模式,所以它们是理想的表单校验器,也可以支持 JavaScript 中许多其它的用途。
正则表达式相当复杂,我们不打算在本文中详尽地教你。
下面是一些例子,让你对它们的工作原理有个基本的了解:
a
— 匹配一个字符a
(不能匹配 b
, aa
等等.)abc
— 匹配 a
, 其次 b
, 最后 c
.a*
— 匹配0个或者多个字符 a
(+
代表至少匹配一个或者多个).[^a]
— 匹配一个字符,但它不能是a
.a|b
— 匹配一个字符 a
或者 b
.[abc]
— 匹配一个字符,它可以是a
,b
或c
.[^abc]
— 匹配一个字符,但它不可以是a
,b
或c
.[a-z]
— 匹配字符范围 a-z
且全部小写 (你可以使用 [A-Za-z]
涵盖大小写, 或 [A-Z]
来限制必须大写).a.c
— 匹配字符 a
,中间匹配任意一个字符,最后匹配字符 c
.a{5}
— 匹配字符 a
五次.a{5,7}
— 匹配字符 a
五到七次,不能多或者少.你也可以在这些表达式中使用数字和其他字符, 例如:
[ -]
— 匹配一个空格或者虚线.[0-9]
— 匹配数字范围0~9.你可以任意地组合这些,你可以任意指定不同的部分:
[Ll].*k
— 匹配一个大写L
或者小写的l
, 之后匹配0个或多个任意类型的字符, 最后匹配一个小写字母 k.[A-Z][A-Za-z' -]+
— 一个大写字母后面跟着匹配一个及以上的大小写字母或者中划线或者撇号或者空格. 这个可以用于校验英语会话中城市或城镇名, 但这需要首字母以大写开头,不包括其他字符,例如来自英国的Manchester, Ashton-under-lyne, 以及Bishop's Stortford等.[0-9]{3}[ -][0-9]{3}[ -][0-9]{4}
— 简单的匹配一个美国内的电话号码 — 三个数字 0-
9, 后面跟着一个空格或者中划线, 之后匹配三个数字 0-
9, 再跟着一个空格或者中划线, 最后跟着四个数字 0-
9. 但实际情况可能更加复杂,因为有些人会给号码加上括号什么的,这里的表达式只是用来做一个简单的演示.不管怎么说, 让我们来实现这些例子 — 更新你的html文档表单增加一个 pattern
属性, 如下:
<form> <label for="choose">Would you prefer a banana or a cherry?</label> <input id="choose" name="i_like" required pattern="banana|cherry"> <button>Submit</button> </form>
input:invalid { border: 2px dashed red; } input:valid { border: 2px solid black; }
{{EmbedLiveSample("使用正则表达式校验", "100%", 50)}}
这个例子中, 该 {{HTMLElement("input")}} 元素接受两个值中的一个: 字符串 "banana" 或者字符串"cherry".
在这个基础上, 尝试把pattern
属性内部的表达式改变成上面的几个例子, 然后看看这些表达式如何影响您可以输入的值以使输入值有效. 尝试写一些你自己设计的,看看它如何工作。尽量让他们与水果有关这样你的例子才会有意义.
注意: 一些 {{HTMLElement("input")}} 元素类型不需要{{htmlattrxref("pattern","input")}} 属性进行校验. 指定特定 email
类型 就会使用匹配电子邮件格式的正则表达式来校验(如果有 {{htmlattrxref("multiple","input")}} 属性请用逗号来分割多个邮箱). 进一步来说, 字段 url
类型则会自动校验输入的是否为一个合法的链接.
注意: 该 {{HTMLElement("textarea")}} 元素不支持{{htmlattrxref("pattern","input")}} 属性.
所有文本框 ({{HTMLElement("input")}} 或 {{HTMLElement("textarea")}}) 都可以使用{{htmlattrxref("minlength","input")}} 和 {{htmlattrxref("maxlength","input")}} 属性来限制长度. 如果输入的字段长度小于 {{htmlattrxref("minlength","input")}} 的值或大于 {{htmlattrxref("maxlength","input")}} 值则无效. 浏览器通常不会让用户在文本字段中键入比预期更长的值,不过更精细的设置总归是更好的。
在数字条目中 (i.e. <input type="number">
), 该 {{htmlattrxref("min","input")}} 和 {{htmlattrxref("max","input")}} 属性同样提供校验约束.如果字段的值小于{{htmlattrxref("min","input")}} 属性的值或大于 {{htmlattrxref("max","input")}} 属性的值,该字段则无效.
让我来看看另外一个例子. 创建一个 fruit-start.html 文件副本.
现在删除 <body>
元素中的内容, 替换成下面的代码:
<form> <div> <label for="choose">Would you prefer a banana or a cherry?</label> <input id="choose" name="i_like" required minlength="6" maxlength="6"> </div> <div> <label for="number">How many would you like?</label> <input type="number" id="number" name="amount" value="1" min="1" max="10"> </div> <div> <button>Submit</button> </div> </form>
text
条目的属性minlength
和 maxlength
都为6 — 这 banana 和 cherry的长度都为6. 输入少于这个长度的字符显示无效, 大多浏览器不能输入超过该限制的长度的字符.number
条目数值限制在 min
为 1 和 一个 max
为 10 中 — 输入超出范围则显示无效, 并且您将无法使用递增/递减箭头将该值改变到此范围之外。input:invalid { border: 2px dashed red; } input:valid { border: 2px solid black; } div { margin-bottom: 10px; }
这里是运行的例子:
{{EmbedLiveSample("限制输入的长度", "100%", 70)}}
注意: <input type="number">
(或者其他类型, 像 range
) 也可以获取到一个{{htmlattrxref("step", "input")}} 属性, 指定了值在增减过程固定改变的值 (如向上增加和向下减少的按钮).
这里就是一个完整的展示 HTML 中使用校验属性的例子:
<form> <p> <fieldset> <legend>Title<abbr title="This field is mandatory">*</abbr></legend> <input type="radio" required name="title" id="r1" value="Mr"><label for="r1">Mr.</label> <input type="radio" required name="title" id="r2" value="Ms"><label for="r2">Ms.</label> </fieldset> </p> <p> <label for="n1">How old are you?</label> <!-- 这里的pattern属性可以用作不支持number类input浏览器的备用方法 请注意当与数字输入框一起使用时,支持pattern属性的浏览器会使它沉默失效。 它仅仅是在这里用作备用 --> <input type="number" min="12" max="120" step="1" id="n1" name="age" pattern="\d+"> </p> <p> <label for="t1">What's your favorite fruit?<abbr title="This field is mandatory">*</abbr></label> <input type="text" id="t1" name="fruit" list="l1" required pattern="[Bb]anana|[Cc]herry|[Aa]pple|[Ss]trawberry|[Ll]emon|[Oo]range"> <datalist id="l1"> <option>Banana</option> <option>Cherry</option> <option>Apple</option> <option>Strawberry</option> <option>Lemon</option> <option>Orange</option> </datalist> </p> <p> <label for="t2">What's your e-mail?</label> <input type="email" id="t2" name="email"> </p> <p> <label for="t3">Leave a short message</label> <textarea id="t3" name="msg" maxlength="140" rows="5"></textarea> </p> <p> <button>Submit</button> </p> </form>
body { font: 1em sans-serif; padding: 0; margin : 0; } form { max-width: 200px; margin: 0; padding: 0 5px; } p > label { display: block; } input[type=text], input[type=email], input[type=number], textarea, fieldset { /* 需要在基于WebKit的浏览器上对表单元素进行恰当的样式设置 */ -webkit-appearance: none; width : 100%; border: 1px solid #333; margin: 0; font-family: inherit; font-size: 90%; -moz-box-sizing: border-box; box-sizing: border-box; } input:invalid { box-shadow: 0 0 5px 1px red; } input:focus:invalid { outline: none; }
{{EmbedLiveSample("完整的例子", "100%", 420)}}
正如我们上面所看到的例子, 每次我们提交无效的表单数据时, 浏览器总会显示错误信息. 但是显示的信息取决于你所使用的浏览器.
这些自动生成的错误有两个缺点:
浏览器 | 渲染 |
---|---|
Firefox 17 (Windows 7) | |
Chrome 22 (Windows 7) | |
Opera 12.10 (Mac OSX) |
要自定义这些消息的外观和文本, 你必须使用 JavaScript; 不能使用 HTML 和 CSS 来改变.
HTML5 提供 constraint validation API 来检测和自定义表单元素的状态. 除此之外,他可以改变错误信息的文本. 让我们快速的看一个例子:
<form> <label for="mail">I would like you to provide me an e-mail</label> <input type="email" id="mail" name="mail"> <button>Submit</button> </form>
在JavaScript 中, 你调用 setCustomValidity()
方法:
var email = document.getElementById("mail"); email.addEventListener("input", function (event) { if (email.validity.typeMismatch) { email.setCustomValidity("I expect an e-mail, darling!"); } else { email.setCustomValidity(""); } });
{{EmbedLiveSample("自定义错误信息", "100%", 50)}}
如果你想控制原生错误信息的界面外观,或者你想处理不支持HTML内置表单校验的浏览器,则必须使用 Javascript。
越来越多的浏览器支持限制校验API,并且这逐渐变得可靠。这些 API 由成组的方法和属性构成,可在特定的表单元素接口上调用:
属性 | 描述 |
---|---|
validationMessage |
一个本地化消息,描述元素不满足校验条件时(如果有的话)的文本信息。如果元素无需校验(willValidate 为 false ),或元素的值满足校验条件时,为空字符串。 |
validity |
一个 {{domxref("ValidityState")}} 对象,描述元素的验证状态。详见有关可能的验证状态的文章。 |
validity.customError |
如果元素设置了自定义错误,返回 true ;否则返回false 。 |
validity.patternMismatch |
如果元素的值不匹配所设置的正则表达式,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.rangeOverflow |
如果元素的值高于所设置的最大值,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.rangeUnderflow |
如果元素的值低于所设置的最小值,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.stepMismatch |
如果元素的值不符合 step 属性的规则,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.tooLong |
如果元素的值超过所设置的最大长度,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.typeMismatch |
如果元素的值出现语法错误,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.valid |
如果元素的值不存在校验问题,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":valid")}} CSS 伪类,否则命中 {{cssxref(":invalid")}} CSS 伪类。 |
validity.valueMissing |
如果元素设置了 required 属性且值为空,返回 true ,否则返回 false 。当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。 |
willValidate |
如果元素在表单提交时将被校验,返回 true ,否则返回 false 。 |
方法 | 描述 |
---|---|
checkValidity() |
如果元素的值不存在校验问题,返回 true ,否则返回 false 。如果元素校验失败,此方法会触发{{event("invalid")}} 事件。 |
{{domxref("HTMLFormElement.reportValidity()")}} | 如果元素或它的子元素控件符合校验的限制,返回 true . 当返回为 false 时, 对每个无效元素可撤销 {{event("invalid")}} 事件会被唤起并且校验错误会报告给用户 。 |
|
为元素添加一个自定义的错误消息;如果设置了自定义错误消息,该元素被认为是无效的,则显示指定的错误。这允许你使用 JavaScript 代码来建立校验失败,而不是用标准约束校验 API 所提供的。这些自定义信息将在向用户报告错误时显示。 如果参数为空,则清空自定义错误。 |
对于旧版浏览器,可以使用 polyfill(例如 Hyperform),来弥补其对约束校验 API 支持的不足。既然你已经使用 JavaScript,在您的网站或 Web 应用程序的设计和实现中使用 polyfill 并不是累赘。
让我们看看如何使用这个 API 来构建自定义错误消息。首先,HTML:
<form novalidate> <p> <label for="mail"> <span>Please enter an email address:</span> <input type="email" id="mail" name="mail"> <span class="error" aria-live="polite"></span> </label> </p> <button>Submit</button> </form>
这个简单的表单使用 {{htmlattrxref("novalidate","form")}} 属性关闭浏览器的自动校验;这允许我们使用脚本控制表单校验。但是,这并不禁止对约束校验 API的支持或是以下 CSS 伪类:{{cssxref(":valid")}}、{{cssxref(":invalid")}}、{{cssxref(":in-range")}} 、{{cssxref(":out-of-range")}} 的应用。这意味着,即使浏览器在发送数据之前没有自动检查表单的有效性,您仍然可以自己做,并相应地设置表单的样式。
aria-live
属性确保我们的自定义错误信息将呈现给所有人,包括使用屏幕阅读器等辅助技术的人。
以下 CSS 样式使我们的表单和其错误输出看起来更有吸引力。
/* 仅为了使示例更好看 */ body { font: 1em sans-serif; padding: 0; margin : 0; } form { max-width: 200px; } p * { display: block; } input[type=email]{ -webkit-appearance: none; width: 100%; border: 1px solid #333; margin: 0; font-family: inherit; font-size: 90%; -moz-box-sizing: border-box; box-sizing: border-box; } /* 校验失败的元素样式 */ input:invalid{ border-color: #900; background-color: #FDD; } input:focus:invalid { outline: none; } /* 错误消息的样式 */ .error { width : 100%; padding: 0; font-size: 80%; color: white; background-color: #900; border-radius: 0 0 5px 5px; -moz-box-sizing: border-box; box-sizing: border-box; } .error.active { padding: 0.3em; }
以下 JavaScript 代码演示如何设置自定义错误校验。
// 有许多方式可以获取 DOM 节点;在此我们获取表单本身和 // email 输入框,以及我们将放置错误信息的 span 元素。 var form = document.getElementsByTagName('form')[0]; var email = document.getElementById('mail'); var error = document.querySelector('.error'); email.addEventListener("input", function (event) { // 当用户输入信息时,校验 email 字段 if (email.validity.valid) { // 如果校验通过,清除已显示的错误消息 error.innerHTML = ""; // 重置消息的内容 error.className = "error"; // 重置消息的显示状态 } }, false); form.addEventListener("submit", function (event) { // 当用户提交表单时,校验 email 字段 if (!email.validity.valid) { // 如果校验失败,显示一个自定义错误 error.innerHTML = "I expect an e-mail, darling!"; error.className = "error active"; // 还需要阻止表单提交事件,以取消数据传送 event.preventDefault(); } }, false);
这是运行结果:
{{EmbedLiveSample("使用校验约束_API_的例子", "100%", 130)}}
约束校验 API 为您提供了一个强大的工具来处理表单校验,让您可以对用户界面进行远超过仅仅使用 HTML 和 CSS所能得到的控制。
有时,例如使用旧版浏览器或自定义小部件,您将无法(或不希望)使用约束校验API。 在这种情况下,您仍然可以使用 JavaScript 来校验您的表单。 校验表单比起真实数据校验更像是一个用户界面问题。
要校验表单,您必须问自己几个问题:
为了说明这一点,让我们重构一下前面的例子,以便它可以在旧版浏览器中使用:
<form> <p> <label for="mail"> <span>Please enter an email address:</span> <input type="text" class="mail" id="mail" name="mail"> <span class="error" aria-live="polite"></span> </label> <p> <!-- Some legacy browsers need to have the `type` attribute explicitly set to `submit` on the `button`element --> <button type="submit">Submit</button> </form>
正如你所看到的,HTML 几乎是一样的;我们只是关闭了 HTML 校验功能。 请注意,ARIA 是与 HTML5 无关的独立规范。
同样的,CSS也不需要太多的改动, 我们只需将 {{cssxref(":invalid")}} 伪类变成真实的类,并避免使用不适用于 Internet Explorer 6 的属性选择器。
/* 仅为了使示例更好看 */ body { font: 1em sans-serif; padding: 0; margin : 0; } form { max-width: 200px; } p * { display: block; } input.mail { -webkit-appearance: none; width: 100%; border: 1px solid #333; margin: 0; font-family: inherit; font-size: 90%; -moz-box-sizing: border-box; box-sizing: border-box; } /* 校验失败的元素样式 */ input.invalid{ border-color: #900; background-color: #FDD; } input:focus.invalid { outline: none; } /* 错误消息的样式 */ .error { width : 100%; padding: 0; font-size: 80%; color: white; background-color: #900; border-radius: 0 0 5px 5px; -moz-box-sizing: border-box; box-sizing: border-box; } .error.active { padding: 0.3em; }
JavaScript 代码有很大的变化,需要做更多的工作。
// 使用旧版浏览器选择 DOM 节点的方法较少 var form = document.getElementsByTagName('form')[0]; var email = document.getElementById('mail'); // 以下是在 DOM 中访问下一个兄弟元素的技巧 // 这比较危险,很容易引起无限循环 // 在现代浏览器中,应该使用 element.nextElementSibling var error = email; while ((error = error.nextSibling).nodeType != 1); // 按照 HTML5 规范 var emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; // 许多旧版浏览器不支持 addEventListener 方法 // 这只是其中一种简单的处理方法 function addEvent(element, event, callback) { var previousEventCallBack = element["on"+event]; element["on"+event] = function (e) { var output = callback(e); // 返回 `false` 来停止回调链,并中断事件的执行 if (output === false) return false; if (typeof previousEventCallBack === 'function') { output = previousEventCallBack(e); if(output === false) return false; } } }; // 现在我们可以重构字段的约束校验了 // 由于不使用 CSS 伪类, 我们必须明确地设置 valid 或 invalid 类到 email 字段上 addEvent(window, "load", function () { // 在这里验证字段是否为空(请记住,该字段不是必需的) // 如果非空,检查它的内容格式是不是合格的 e-mail 地址 var test = email.value.length === 0 || emailRegExp.test(email.value); email.className = test ? "valid" : "invalid"; }); // 处理用户输入事件 addEvent(email, "input", function () { var test = email.value.length === 0 || emailRegExp.test(email.value); if (test) { email.className = "valid"; error.innerHTML = ""; error.className = "error"; } else { email.className = "invalid"; } }); // 处理表单提交事件 addEvent(form, "submit", function () { var test = email.value.length === 0 || emailRegExp.test(email.value); if (!test) { email.className = "invalid"; error.innerHTML = "I expect an e-mail, darling!"; error.className = "error active"; // 某些旧版浏览器不支持 event.preventDefault() 方法 return false; } else { email.className = "valid"; error.innerHTML = ""; error.className = "error"; } });
该结果如下:
{{EmbedLiveSample("不使用内建_API_时的表单校验", "100%", 130)}}
正如你所看到的,建立自己的校验系统并不难。 困难的部分是使其足够通用,以跨平台和任何形式使用它可以创建。 有许多库可用于执行表单校验; 你应该毫不犹豫地使用它们。 这里有一些例子:
在某些情况下,执行一些远程校验可能很有用。 当用户输入的数据与存储在应用程序服务器端的附加数据绑定时,这种校验是必要的。 一个应用实例就是注册表单,在这里你需要一个用户名。 为了避免重复,执行一个 AJAX 请求来检查用户名的可用性,要比让先用户发送数据,然后因为表单重复了返回错误信息要好得多。
执行这样的校验需要采取一些预防措施:
表单校验并不需要复杂的 JavaScript,但它需要对用户的仔细考虑。 一定要记住帮助您的用户更正他提供的数据。 为此,请务必:
{{PreviousMenuNext("Learn/HTML/Forms/Sending_and_retrieving_form_data", "Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms")}}