DOM 表单取值与校验
新增学员页面需要读取姓名、班级、手机号、地址和状态,再整理成 API 需要的 JSON 数据。提交之前,还要检查必填项和字段格式。
本节课解决四个问题:
- 怎样读取不同类型的表单控件。
- 怎样把表单内容整理成 JavaScript 对象。
- 怎样使用 HTML 和 JavaScript 完成浏览器端校验。
- 怎样把错误显示在对应字段旁边。
本节按“读取控件 → 整理数据 → 校验并显示错误”的顺序推进。当前只在浏览器中预检查数据,不发送 API 请求;后面的 fetch 章节再完成真正提交。
前面的短示例围绕相应的 HTML 分别练习;同一控件的不同取值方案用于对比,不要把重复声明的变量全部放进一个脚本。综合案例的完整实现代码则按顺序组合。
表单取值
name 与字段名
表单控件要有 name。name 不只是 HTML 属性,也是前端整理数据和后端接收字段时的重要名称。
<form id="student-form">
<label for="student-name">姓名</label>
<input id="student-name" name="name" type="text" />
<label for="class-name">班级</label>
<select id="class-name" name="class_name">
<option value="">请选择班级</option>
<option value="Python 1 班">Python 1 班</option>
<option value="Python 2 班">Python 2 班</option>
</select>
<button type="submit">保存</button>
</form>接口字段是 class_name,表单中也使用 name="class_name",这样 FormData 收集后的字段可以直接对应接口。id="class-name" 用于关联标签和查询 DOM,不承担请求字段的职责。前面本地对象使用的 className 可以保留,但提交前必须明确转换为 class_name,不能把两者当成同一个属性读取。
文本控件取值
输入框和多行文本框的当前内容都保存在 value 中。
<h1 id="page-title">新增学员</h1>
<input id="student-name" type="text" value="张三" />
<textarea id="student-address">湖南长沙</textarea>const nameInput = document.querySelector("#student-name");
const addressInput = document.querySelector("#student-address");
const pageTitle = document.querySelector("#page-title");
const name = nameInput.value.trim();
const address = addressInput.value.trim();
console.log(name);
console.log(address);运行后,控制台会得到去掉两侧空格后的姓名和地址。表单控件的当前输入使用 value,不要用 textContent 读取。
表单输入使用 value,普通元素文字使用 textContent:
nameInput.value = "李四";
pageTitle.textContent = "编辑学员";提交普通文本前通常调用 trim(),去掉两侧多余空格。
const trimmedName = nameInput.value.trim();
if (!trimmedName) {
console.log("请输入姓名");
}trim() 不会修改输入框原值,只返回处理后的新字符串。如果希望页面也显示处理结果,需要重新赋值:
nameInput.value = nameInput.value.trim();form.elements 查询
已经找到表单后,可以通过 form.elements 按 name 查询内部控件。
const studentForm = document.querySelector("#student-form");
const nameInput = studentForm.elements.namedItem("name");
const classSelect = studentForm.elements.namedItem("class_name");
console.log(nameInput.value);
console.log(classSelect.value);也经常写成:
console.log(studentForm.elements.name.value);
console.log(studentForm.elements.class_name.value);namedItem() 更明确,也能避免字段名与对象自带属性发生混淆。本课程的完整案例使用 namedItem()。
单选框取值
同一组单选框使用相同 name,不同 value。
<form id="student-form">
<label>
<input type="radio" name="gender" value="male" />
男
</label>
<label>
<input type="radio" name="gender" value="female" />
女
</label>
</form>:checked 查询
const checkedGender = document.querySelector(
'input[name="gender"]:checked',
);
const gender = checkedGender ? checkedGender.value : "";没有选中项时,querySelector() 返回 null,所以不能直接读取 .value。
form.elements 读取
const studentForm = document.querySelector("#student-form");
const genderFromForm = studentForm.elements.gender.value;同名的多个单选框会组成 RadioNodeList,读取 .value 可以得到当前选中值。没有选中时得到空字符串。
页面上只需要临时查找选中项时可以使用 :checked;已经拿到整个表单时,使用 form.elements 更集中。两种方式都要处理“没有选中”的情况。
复选框取值
单个复选框
学员状态开关只有一个控件,直接读取 checked:
<label>
<input id="student-disabled" name="disabled" type="checkbox" />
停用学员
</label>const disabledInput = document.querySelector("#student-disabled");
const disabled = disabledInput.checked;
console.log(disabled); // true 或 falsevalue 表示控件提交时使用的字符串,checked 才表示当前是否选中。
一组复选框
<label><input type="checkbox" name="skills" value="html" />HTML</label>
<label><input type="checkbox" name="skills" value="css" />CSS</label>
<label><input type="checkbox" name="skills" value="javascript" />JavaScript</label>读取全部选中值:
const checkedSkills = document.querySelectorAll(
'input[name="skills"]:checked',
);
const skills = Array.from(checkedSkills, (input) => input.value);
console.log(skills);querySelectorAll() 返回 NodeList。Array.from() 可以在转换为数组的同时读取每个控件的 value。
下拉框取值
普通单选下拉框直接读取 value:
<select id="class-name" name="class_name">
<option value="">请选择班级</option>
<option value="Python 1 班">Python 1 班</option>
<option value="Python 2 班">Python 2 班</option>
</select>const classSelect = document.querySelector("#class-name");
console.log(classSelect.value);多选下拉框使用 selectedOptions:
<select id="course-select" name="courses" multiple>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="javascript">JavaScript</option>
</select>const courseSelect = document.querySelector("#course-select");
const courses = Array.from(
courseSelect.selectedOptions,
(option) => option.value,
);普通下拉框只有一个值,读取 value 即可;带 multiple 的下拉框可能选中多项,需要遍历 selectedOptions。
文件控件取值
文件输入框不能通过普通 value 得到可上传文件。使用 files:
<input id="avatar" name="avatar" type="file" accept="image/*" />const avatarInput = document.querySelector("#avatar");
const avatarFile = avatarInput.files[0];
if (avatarFile) {
console.log(avatarFile.name);
console.log(avatarFile.type);
console.log(avatarFile.size);
}files 是 FileList。没有选择文件时,files[0] 是 undefined。
本节只读取文件信息。图片预览、大小检查、FormData 上传放到后面的文件上传课。
FormData 字段收集
如果控件都在同一个 <form> 中,并且有 name,可以创建 FormData:
const studentForm = document.querySelector("#student-form");
const formData = new FormData(studentForm);
console.log(formData.get("name"));
console.log(formData.get("class_name"));普通单值字段可以转换成对象:
const data = Object.fromEntries(formData.entries());
console.log(data);但要注意:
- 所有普通字段值默认都是字符串。
- 未选中的复选框不会出现在
FormData中。 - 同名复选框需要使用
formData.getAll(name)。 - 没有
name的控件不会被收集。 disabled控件不会被收集。- 文件字段得到的是
File对象。
const skills = formData.getAll("skills");
const disabled = formData.has("disabled");当前学员 API 使用 JSON,并要求 disabled 是布尔值,所以完整案例会主动整理数据对象,不直接使用 Object.fromEntries() 作为最终结果。
FormData 很适合快速查看浏览器会收集哪些字段,也适合后面的文件上传;当前 JSON 案例仍然手动整理数据,确保字段名、字符串处理和布尔值都符合接口要求。
表单校验与错误反馈
HTML 约束校验
浏览器原生支持常用校验属性:
<input
name="name"
type="text"
required
minlength="2"
maxlength="20"
/>
<input
name="mobile"
type="tel"
required
pattern="1[3-9][0-9]{9}"
/>| 属性 | 作用 |
|---|---|
required | 必填 |
minlength | 最少字符数 |
maxlength | 最多字符数 |
min、max | 数字或日期范围 |
pattern | 使用正则表达式检查格式 |
type="email" | 检查基础邮箱格式 |
type="url" | 检查基础 URL 格式 |
浏览器提交表单时,会自动检查这些约束。
使用 JavaScript 主动检查:
const studentForm = document.querySelector("#student-form");
console.log(studentForm.checkValidity());
studentForm.reportValidity();| 方法 | 作用 |
|---|---|
checkValidity() | 返回是否通过,不主动弹出全部提示 |
reportValidity() | 检查并显示浏览器提示 |
前端校验只用于尽早提醒用户,后端仍然必须重新校验。用户可以绕过浏览器代码直接调用 API。
validity 校验状态
每个支持校验的表单控件都有 validity:
const mobileInput = studentForm.elements.namedItem("mobile");
console.log(mobileInput.validity.valid);
console.log(mobileInput.validity.valueMissing);
console.log(mobileInput.validity.patternMismatch);
console.log(mobileInput.validationMessage);常用状态:
| 状态 | 含义 |
|---|---|
valid | 全部约束通过 |
valueMissing | required 字段为空 |
tooShort | 少于 minlength |
tooLong | 超过 maxlength |
typeMismatch | 不符合 email、url 等类型 |
patternMismatch | 不符合 pattern |
rangeUnderflow | 小于 min |
rangeOverflow | 大于 max |
customError | setCustomValidity() 设置了错误 |
这些状态适合根据不同错误显示更清楚的中文提示。
setCustomValidity 自定义提示
setCustomValidity() 可以设置业务错误:
const mobileInput = studentForm.elements.namedItem("mobile");
mobileInput.setCustomValidity("");
if (!/^1[3-9]\d{9}$/.test(mobileInput.value.trim())) {
mobileInput.setCustomValidity("请输入正确的 11 位手机号");
}
mobileInput.reportValidity();每次重新校验前必须先清空旧错误:
mobileInput.setCustomValidity("");如果没有清空,即使用户已经修改正确,控件仍然会保持无效状态。
字段错误反馈
管理后台通常需要把错误固定显示在字段下面,而不是只使用浏览器气泡。
<div class="form-item">
<label for="student-name">姓名</label>
<input
id="student-name"
name="name"
type="text"
aria-describedby="name-error"
/>
<small id="name-error" class="form-error"></small>
</div>显示错误:
function showFieldError(input, errorElement, message) {
input.classList.add("is-invalid");
input.setAttribute("aria-invalid", "true");
errorElement.textContent = message;
}清除错误:
function clearFieldError(input, errorElement) {
input.classList.remove("is-invalid");
input.removeAttribute("aria-invalid");
errorElement.textContent = "";
}错误文字使用 textContent,不要使用 innerHTML。
aria-describedby="name-error" 把输入框与说明区域关联起来;aria-invalid="true" 表示当前输入有误。两者不会替你执行校验,也不会自动显示文字,需要与上面的样式和错误消息一起更新。
提交失败时,先显示全部字段错误,再把焦点移到第一个无效控件;清除错误时同步移除 aria-invalid。这样键盘用户能直接修改问题,辅助技术也能读到对应说明。后面接收 API 的 422 错误时,仍可复用这些字段旁的错误区域。
综合案例:学员表单预检查
提交表单时完成下面的步骤:
- 阻止浏览器刷新页面。
- 读取并整理学员数据。
- 清除上一次错误。
- 校验姓名、班级、手机号和地址。
- 把错误显示到对应字段下面。
- 校验通过后,在页面显示将要提交的 JSON。
本案例暂时不调用 API。下一阶段学习 fetch 后,再把数据发送给 POST /api/students。
表单结构
<form id="student-form" novalidate>
<div class="form-item">
<label for="student-name">姓名</label>
<input
id="student-name"
name="name"
type="text"
autocomplete="name"
aria-describedby="name-error"
/>
<small id="name-error" class="form-error"></small>
</div>
<div class="form-item">
<label for="class-name">班级</label>
<select id="class-name" name="class_name" aria-describedby="class-name-error">
<option value="">请选择班级</option>
<option value="Python 1 班">Python 1 班</option>
<option value="Python 2 班">Python 2 班</option>
<option value="Web 全栈班">Web 全栈班</option>
</select>
<small id="class-name-error" class="form-error"></small>
</div>
<div class="form-item">
<label for="student-mobile">手机号</label>
<input
id="student-mobile"
name="mobile"
type="tel"
autocomplete="tel"
aria-describedby="mobile-error"
/>
<small id="mobile-error" class="form-error"></small>
</div>
<div class="form-item">
<label for="student-address">地址</label>
<textarea
id="student-address"
name="address"
rows="3"
aria-describedby="address-error"
></textarea>
<small id="address-error" class="form-error"></small>
</div>
<label class="checkbox-label">
<input name="disabled" type="checkbox" />
停用学员
</label>
<button type="submit">检查数据</button>
<button id="reset-button" type="button">重置</button>
</form>
<p id="empty-result">请先填写并检查表单</p>
<pre id="result" hidden></pre>novalidate 表示这个完整案例由 JavaScript 显示自定义错误。四个 aria-describedby 分别关联字段旁的错误区域;脚本显示错误时还会设置 aria-invalid。前面讲解的浏览器原生校验仍然有效,两种方式按项目需要选择。
字段配置
const form = document.querySelector("#student-form");
const result = document.querySelector("#result");
const emptyResult = document.querySelector("#empty-result");
const fieldConfig = {
name: {
input: form.elements.namedItem("name"),
error: document.querySelector("#name-error"),
},
class_name: {
input: form.elements.namedItem("class_name"),
error: document.querySelector("#class-name-error"),
},
mobile: {
input: form.elements.namedItem("mobile"),
error: document.querySelector("#mobile-error"),
},
address: {
input: form.elements.namedItem("address"),
error: document.querySelector("#address-error"),
},
};fieldConfig 把每个字段与对应错误区域放在一起。后面清除错误、显示错误和聚焦第一个错误字段时,都可以按字段名复用这份配置。
数据整理
function getStudentFormData(form) {
const nameInput = form.elements.namedItem("name");
const classSelect = form.elements.namedItem("class_name");
const mobileInput = form.elements.namedItem("mobile");
const addressInput = form.elements.namedItem("address");
const disabledInput = form.elements.namedItem("disabled");
return {
name: nameInput.value.trim(),
class_name: classSelect.value,
mobile: mobileInput.value.trim(),
address: addressInput.value.trim(),
disabled: disabledInput.checked,
};
}函数返回的是普通 JavaScript 对象:文本字段去掉两侧空格,disabled 保持布尔值。这正是后面发送 JSON 时需要的结构。
数据校验
function validateStudentData(data) {
const errors = {};
if (data.name.length < 2) {
errors.name = "姓名至少需要 2 个字符";
} else if (data.name.length > 20) {
errors.name = "姓名不能超过 20 个字符";
}
if (!data.class_name) {
errors.class_name = "请选择班级";
}
if (!/^1[3-9]\d{9}$/.test(data.mobile)) {
errors.mobile = "请输入正确的 11 位手机号";
}
if (data.address.length > 100) {
errors.address = "地址不能超过 100 个字符";
}
return errors;
}errors 的键与 fieldConfig 的键保持一致。没有错误时返回空对象;有错误时,例如 errors.mobile 会保存手机号提示文字。
错误与预览状态
function clearErrors() {
for (const config of Object.values(fieldConfig)) {
config.input.classList.remove("is-invalid");
config.input.removeAttribute("aria-invalid");
config.error.textContent = "";
}
}
function showErrors(errors) {
for (const [field, message] of Object.entries(errors)) {
const config = fieldConfig[field];
config.input.classList.add("is-invalid");
config.input.setAttribute("aria-invalid", "true");
config.error.textContent = message;
}
}提交前先调用 clearErrors(),再调用 showErrors(errors)。这样用户修正后,旧提示和红色边框不会残留。
清空提交预览
function clearResult() {
result.textContent = "";
result.hidden = true;
emptyResult.hidden = false;
}每次重新检查或重置表单时,先隐藏旧 JSON,恢复“请先填写并检查表单”的提示。
提交与重置
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = getStudentFormData(form);
const errors = validateStudentData(data);
clearErrors();
clearResult();
if (Object.keys(errors).length > 0) {
showErrors(errors);
const firstErrorField = Object.keys(errors)[0];
fieldConfig[firstErrorField].input.focus();
return;
}
result.textContent = JSON.stringify(data, null, 2);
result.hidden = false;
emptyResult.hidden = true;
});下面只抽出表单提交的固定结构,供回看时对照,不要和上面的完整监听器重复绑定:
form.addEventListener("submit", (event) => {
event.preventDefault();
// 读取、校验和处理数据
});上一节已经学习 submit、事件对象和默认行为,本节把它们用于表单读取与校验。
重置表单
const resetButton = document.querySelector("#reset-button");
resetButton.addEventListener("click", () => {
form.reset();
clearErrors();
clearResult();
form.elements.namedItem("name").focus();
});重置不仅恢复控件初始值,也要清除错误和旧预览。最后把焦点放回姓名输入框,用户可以直接重新填写。
运行与验收
打开学员表单预检查(源码文件:./.code/p04/c06-form-dom/04-form-precheck.html)后,直接点击“检查数据”,姓名、班级和手机号下方应显示错误,第一个错误字段会自动获得焦点。
可以按下面步骤验证:
- 填写姓名“张三”、班级“Python 1 班”、手机号“13800000001”,点击检查,应在右侧看到包含
disabled: false的 JSON。 - 勾选“停用学员”后再次检查,JSON 中的
disabled应变为布尔值true,不是字符串"true"。 - 将姓名改为 1 个字符,再改为超过 20 个字符;两次检查都应看到姓名错误。恢复为“张三”后,姓名错误应消失。
- 在地址中输入超过 100 个字符,检查后应看到地址错误;删至 100 个字符以内后再次检查,错误应消失。
- 输入错误手机号后检查,再改为正确手机号重新检查,红色边框和错误文字应消失。
- 点击重置,表单、错误提示和 JSON 预览应恢复初始状态,姓名输入框重新获得焦点。
完整附件还包含页面样式:
学员表单预检查
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DOM 表单取值与校验</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #f5f7fa;
color: #303133;
font-family: Arial, "Microsoft YaHei", sans-serif;
}
.page-container {
display: grid;
grid-template-columns: minmax(0, 560px) minmax(0, 1fr);
gap: 24px;
width: min(1040px, calc(100% - 32px));
margin: 48px auto;
}
.form-panel,
.result-panel {
padding: 28px;
border-radius: 10px;
background: #fff;
box-shadow: 0 6px 20px rgb(0 0 0 / 6%);
}
.panel-title {
margin: 0 0 24px;
font-size: 24px;
}
.form-item {
margin-bottom: 18px;
}
.form-item label {
display: block;
margin-bottom: 8px;
font-weight: 600;
}
.form-item input,
.form-item select,
.form-item textarea {
display: block;
width: 100%;
padding: 10px 12px;
border: 1px solid #dcdfe6;
border-radius: 5px;
color: #303133;
font: inherit;
outline: none;
}
.form-item input:focus,
.form-item select:focus,
.form-item textarea:focus {
border-color: #16baaa;
}
.form-item .is-invalid {
border-color: #f56c6c;
}
.form-error {
display: block;
min-height: 18px;
margin-top: 5px;
color: #f56c6c;
font-size: 13px;
}
.checkbox-label {
display: inline-flex;
align-items: center;
gap: 7px;
margin-bottom: 22px;
cursor: pointer;
}
.button-group {
display: flex;
gap: 10px;
}
button {
padding: 10px 20px;
border: 1px solid transparent;
border-radius: 5px;
font: inherit;
cursor: pointer;
}
button[type="submit"] {
background: #16baaa;
color: #fff;
}
#reset-button {
border-color: #dcdfe6;
background: #fff;
color: #606266;
}
.result-description {
margin: -12px 0 20px;
color: #606266;
line-height: 1.7;
}
#result {
min-height: 240px;
margin: 0;
padding: 18px;
overflow: auto;
border-radius: 6px;
background: #1f2329;
color: #d6e4ff;
line-height: 1.6;
}
.empty-result {
padding: 70px 20px;
border: 1px dashed #dcdfe6;
border-radius: 6px;
color: #909399;
text-align: center;
}
@media (max-width: 860px) {
.page-container {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main class="page-container">
<section class="form-panel">
<h1 class="panel-title">新增学员</h1>
<form id="student-form" novalidate>
<div class="form-item">
<label for="student-name">姓名</label>
<input
id="student-name"
name="name"
type="text"
autocomplete="name"
aria-describedby="name-error"
placeholder="请输入姓名"
/>
<small id="name-error" class="form-error"></small>
</div>
<div class="form-item">
<label for="class-name">班级</label>
<select
id="class-name"
name="class_name"
aria-describedby="class-name-error"
>
<option value="">请选择班级</option>
<option value="Python 1 班">Python 1 班</option>
<option value="Python 2 班">Python 2 班</option>
<option value="Web 全栈班">Web 全栈班</option>
</select>
<small id="class-name-error" class="form-error"></small>
</div>
<div class="form-item">
<label for="student-mobile">手机号</label>
<input
id="student-mobile"
name="mobile"
type="tel"
autocomplete="tel"
aria-describedby="mobile-error"
placeholder="请输入 11 位手机号"
/>
<small id="mobile-error" class="form-error"></small>
</div>
<div class="form-item">
<label for="student-address">地址</label>
<textarea
id="student-address"
name="address"
rows="3"
aria-describedby="address-error"
placeholder="地址可以不填写"
></textarea>
<small id="address-error" class="form-error"></small>
</div>
<label class="checkbox-label">
<input name="disabled" type="checkbox" />
停用学员
</label>
<div class="button-group">
<button type="submit">检查数据</button>
<button id="reset-button" type="button">重置</button>
</div>
</form>
</section>
<section class="result-panel">
<h2 class="panel-title">提交预览</h2>
<p class="result-description">
校验通过后,这里会显示准备发送给测试 API 的 JSON 数据。
</p>
<p id="empty-result" class="empty-result">请先填写并检查表单</p>
<pre id="result" hidden></pre>
</section>
</main>
<script>
const form = document.querySelector("#student-form");
const resetButton = document.querySelector("#reset-button");
const result = document.querySelector("#result");
const emptyResult = document.querySelector("#empty-result");
const fieldConfig = {
name: {
input: form.elements.namedItem("name"),
error: document.querySelector("#name-error"),
},
class_name: {
input: form.elements.namedItem("class_name"),
error: document.querySelector("#class-name-error"),
},
mobile: {
input: form.elements.namedItem("mobile"),
error: document.querySelector("#mobile-error"),
},
address: {
input: form.elements.namedItem("address"),
error: document.querySelector("#address-error"),
},
};
function getStudentFormData(form) {
const nameInput = form.elements.namedItem("name");
const classSelect = form.elements.namedItem("class_name");
const mobileInput = form.elements.namedItem("mobile");
const addressInput = form.elements.namedItem("address");
const disabledInput = form.elements.namedItem("disabled");
return {
name: nameInput.value.trim(),
class_name: classSelect.value,
mobile: mobileInput.value.trim(),
address: addressInput.value.trim(),
disabled: disabledInput.checked,
};
}
function validateStudentData(data) {
const errors = {};
if (data.name.length < 2) {
errors.name = "姓名至少需要 2 个字符";
} else if (data.name.length > 20) {
errors.name = "姓名不能超过 20 个字符";
}
if (!data.class_name) {
errors.class_name = "请选择班级";
}
if (!/^1[3-9]\d{9}$/.test(data.mobile)) {
errors.mobile = "请输入正确的 11 位手机号";
}
if (data.address.length > 100) {
errors.address = "地址不能超过 100 个字符";
}
return errors;
}
function clearErrors() {
for (const config of Object.values(fieldConfig)) {
config.input.classList.remove("is-invalid");
config.input.removeAttribute("aria-invalid");
config.error.textContent = "";
}
}
function showErrors(errors) {
for (const [field, message] of Object.entries(errors)) {
const config = fieldConfig[field];
config.input.classList.add("is-invalid");
config.input.setAttribute("aria-invalid", "true");
config.error.textContent = message;
}
}
function clearResult() {
result.textContent = "";
result.hidden = true;
emptyResult.hidden = false;
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = getStudentFormData(form);
const errors = validateStudentData(data);
clearErrors();
clearResult();
if (Object.keys(errors).length > 0) {
showErrors(errors);
const firstErrorField = Object.keys(errors)[0];
fieldConfig[firstErrorField].input.focus();
return;
}
result.textContent = JSON.stringify(data, null, 2);
result.hidden = false;
emptyResult.hidden = true;
});
resetButton.addEventListener("click", () => {
form.reset();
clearErrors();
clearResult();
form.elements.namedItem("name").focus();
});
</script>
</body>
</html>小结
本节课最核心的是掌握六件事:
- 文本框、下拉框和多行文本框读取
value。 - 单个复选框读取
checked,复选框组查询:checked。 - 文件输入框读取
files,不能依赖普通value。 - 使用独立函数把 DOM 表单整理成 API 需要的数据对象。
- HTML 约束适合基础校验,JavaScript 适合项目自定义错误。
- 前端校验不能替代后端校验,API 错误仍要单独处理。