千古前端图文教程:DOM操作基础练习——事件驱动、className切换与表单属性实战
千古前端图文教程:DOM操作基础练习——事件驱动、className切换与表单属性实战
本篇实战指南围绕《08-前端基本功:CSS和DOM练习》中的「DOM操作练习:基础练习」展开,用 8 个可独立运行的最小示例,系统训练原生 JavaScript 操作 DOM 的核心套路:获取事件源 → 绑定事件 → 书写事件驱动程序。你将掌握通过 className 切换元素显隐与样式、通过 src/innerHTML 修改元素内容、批量给一组元素绑定事件、操作表单控件(disabled、value、checked、焦点事件)等高频能力。每个示例都配有完整可复制的 HTML 代码,可直接保存为 .html 文件在浏览器中运行验证。
一、练习前的知识铺垫:DOM、事件与"三步法"
本练习隶属于项目中的「前端基本功:CSS 和 DOM 练习」系列(见 04-JavaScript基础/36-DOM简介和DOM操作.md)。在动手写代码之前,先把三个核心概念理清楚。
1. 什么是 DOM
DOM(Document Object Model,文档对象模型)为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构。其目的就是为 JS 操作 HTML 元素制定一个规范。HTML 加载完毕后,渲染引擎会在内存中把 HTML 文档生成一棵 DOM 树,getElementById 获取的就是内存中 DOM 树上的元素节点,操作时修改的是该元素的属性。
在 DOM 中一切都是节点:整个 HTML 文档是文档节点,标签是元素节点,属性是属性节点,文本内容是文本节点。所有节点都是 Object,节点类型不同,属性和方法也各不相同。
2. 事件的三要素与"三步法"
DOM 可以做四类事情:找对象(元素节点)、设置元素的属性值、设置元素的样式、动态创建和删除元素。而要让页面"动"起来,靠的是事件:事件的触发响应包含三个要素——事件源(谁触发)、事件(触发什么,如点击 onclick)、事件的驱动程序(触发后干什么)。
本篇文章所有示例都遵循同一个三步套路:
// 1.获取事件源和相关元素
var btn = document.getElementById("btn");
// 2.绑定事件
btn.onclick = function () {
// 3.书写事件驱动程序
// ...
}
3. 事件绑定方式与文档加载时机
示例中大量使用 元素.onclick = function(){} 这种绑定方式,它属于 DOM0 级别的事件写法。在 04-JavaScript基础/41-事件的绑定和事件对象Event.md 中详细对比了三种方式:
element.onclick = fn(DOM0):简单直观,但一个元素的一个事件只能绑定一个响应函数,后绑定的会覆盖先绑定的;element.addEventListener('click', fn, false)(DOM2):可绑定多个响应函数且不覆盖,第三参数true表示捕获阶段触发、false表示冒泡阶段触发(默认);element.attachEvent('onclick', fn):仅用于 IE8 及以下浏览器。
另外要注意文档加载顺序:浏览器自上而下加载页面,如果 <script> 写在 <head> 里,此时 DOM 尚未加载完毕,直接获取元素会拿不到。两种常见解法(详见 文档的加载 一节):把脚本放到 <body> 底部,或者用 window.onload = function(){} 包裹——onload 事件会在整个页面加载完成之后才触发,确保执行代码时所有 DOM 对象都已加载完毕。
二、举例1:点击按钮时,显示和隐藏盒子
需求分析
页面上有一个按钮和一个粉色盒子。点击按钮:盒子隐藏,按钮文字变为"显示";再次点击:盒子显示,按钮文字变回"隐藏"。如此循环往复。
代码实现
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
button {
margin: 10px;
}
div {
width: 200px;
height: 200px;
background-color: pink;
}
.show {
display: block;
}
.hide {
display: none;
}
</style>
</head>
<body>
<button id="btn">隐藏</button>
<div>
千古壹号
</div>
<script>
//需求:点击button,隐藏盒子。改变文字,在点击按钮,显示出来。
//步骤:
//1.获取事件源和相关元素
//2.绑定事件
//3.书写事件驱动程序
//1.获取事件源和相关元素
var btn = document.getElementById("btn");
var div1 = document.getElementsByTagName("div")[0];
//2.绑定事件
btn.onclick = function () {
//3.书写事件驱动程序
//判断btn的innerHTML属性值,如果为隐藏就隐藏盒子,并修改按钮内容为显示。
//反之,则显示,并修改按钮内容为隐藏
if (this.innerHTML === "隐藏") {
div1.className = "hide";
//修改按钮上的文字(innerHTML)
btn.innerHTML = "显示";
} else {
div1.className = "show";
//修改按钮上的文字(innerHTML)
btn.innerHTML = "隐藏";
}
}
</script>
</body>
</html>
代码解释
- 获取元素:这里同时用到了两种获取方式。
document.getElementById("btn")通过 id 获取单个元素节点(id 是唯一的);document.getElementsByTagName("div")<a href="https://link.gitcode.com/i/98ec919d7cf1eff46d3457fc4a00ab68" target="_blank">0]通过标签名获取元素节点数组(注意方法名里有 s),由于页面里只有一个 div,所以取下标[0]。更完整的三种获取方式可以回顾 [元素节点的获取 一节:getElementById、getElementsByTagName、getElementsByClassName。 - 显隐切换的本质:CSS 里预先定义好
.show { display: block; }和.hide { display: none; }两个类,JS 通过修改元素的className属性来切换盒子的显示与隐藏。这是 DOM 操作中非常经典的做法——样式先写死在 CSS 里,JS 只负责换类名,后续的 Tab 栏切换也是同一思想。 - 逻辑判断:当盒子是显示状态时就设为隐藏,隐藏状态时就设为显示。判断的依据是按钮当前的文字:
this.innerHTML === "隐藏"。这里this指向函数的调用者——也就是按钮btn本身。 - innerHTML 的妙用:
innerHTML是双闭合标签里面的内容(包含标签),这里用它动态修改按钮上显示的文字,实现"隐藏 ⇄ 显示"的文案切换。注意原文档里写的是innerHTHL,正确拼写应为innerHTML。
踩坑提示:与
innerHTML类似的是innerText(只取文本、不包含标签)。在 DOM对象的属性-补充 一节有专门对比:innerHTML会获取/修改标签本身,innerText则不会。向元素追加内容时,+=是在原有基础上增加,=是整体替换,二者含义截然不同。
三、举例2:美女相册——for 循环批量绑定事件
需求与素材准备
在真正的图片素材做好之前,可以用在线占位图片生成网站 placeholder.com 临时占位(例如在链接中指定尺寸参数即可生成对应大小的占位图),好处是素材做出来之前先留出空位,方便以后换图。
页面需求有两条:
- 点击小图片,改变下方大图片的
src属性值,让其赋值为 a 链接中的href属性值; - 让
p标签的innerHTML属性值,变成 a 标签的title属性值。
代码实现
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style type="text/css">
body {
font-family: "Helvetica", "Arial", serif;
color: #333;
margin: 1em 10%;
}
h1 {
color: #333;
background-color: transparent;
}
a {
color: #c60;
background-color: transparent;
font-weight: bold;
text-decoration: none;
}
ul {
padding: 0;
}
li {
float: left;
padding: 1em;
list-style: none;
}
#imagegallery {
list-style: none;
}
#imagegallery li {
margin: 0px 20px 20px 0px;
padding: 0px;
display: inline;
}
#imagegallery li a img {
border: 0;
}
</style>
</head>
<body>
<h2>
美女画廊
</h2>
<a href="#">注册</a>
<ul id="imagegallery">
<li>
<a href="image/1.jpg" title="美女A">
<img src="image/1-small.jpg" width="100" alt="美女1"/>
</a>
</li>
<li>
<a href="image/2.jpg" title="美女B">
<img src="image/2-small.jpg" width="100" alt="美女2"/>
</a>
</li>
<li>
<a href="image/3.jpg" title="美女C">
<img src="image/3-small.jpg" width="100" alt="美女3"/>
</a>
</li>
<li>
<a href="image/4.jpg" title="美女D">
<img src="image/4-small.jpg" width="100" alt="美女4"/>
</a>
</li>
</ul>
<div style="clear:both"></div>
<img id="image" src="image/placeholder.png" width="450px"/>
<p id="des">选择一个图片</p>
<script>
//需求:
//(1)点击小图片,改变下面的大图片的src属性值,让其赋值为a链接中的href属性值。
//(2)让p标签的innnerHTML属性值,变成a标签的title属性值。
//1.获取事件源和相关元素
//利用元素获取其下面的标签。
var ul = document.getElementById("imagegallery");
var aArr = ul.getElementsByTagName("a"); //获取ul中的超链接<a>
// console.log(aArr[0]);
var img = document.getElementById("image");
var des = document.getElementById("des");
//2.绑定事件
//以前是一个一个绑定,但是现在是一个数组。我们用for循环绑定
for (var i = 0; i < aArr.length; i++) {
aArr[i].onclick = function () {
//3.【核心代码】书写事件驱动程序:修改属性值
img.src = this.href; //this指的是函数调用者,和i并无关系,所以不会出错。
// img.src = aArr[i].href; 注意,上面这一行代码不要写成这样
des.innerHTML = this.title;
return false; //return false表示:阻止继续执行下面的代码。
}
}
</script>
</body>
</html>
代码解释
(1)获取事件源:通过 ul.getElementsByTagName("a") 获取 ul 里面的 a 元素。注意这里不是从 document 获取,而是从已获取的元素 ul 往下找,这体现了 DOM 的层级访问关系——元素可以用同样的方法获取其子孙元素。
(2)绑定事件:aArr 是一个元素数组,要给每个 a 都绑定点击事件,不能再一个个写,于是用 for 循环批量绑定。
(3)【重要】事件驱动程序中 this 的用法:核心代码是 img.src = this.href,而不是 img.src = aArr[i].href。原因在于:this 指的是函数的调用者,也就是当前被点击的那个 a 标签;而 aArr[i].href 中的 i 是循环变量——当循环结束后 i 已经变成 4,任何一次点击都会读取 aArr[4],永远取不到正确的那张图(这就是典型的闭包陷阱问题)。所以在事件回调函数里,请优先使用 this 来引用当前触发事件的元素。
(4)return false:代码最后一行 return false 表示阻止继续执行下面的代码。在 onclick 这种 DOM0 事件中,return false 相当于阻止了 a 标签的默认行为(跳转到 href 指向的链接),防止页面因为点击小图而真的跳转到 image/1.jpg。在 41-事件的绑定和事件对象Event.md 中还有更规范的做法:在事件对象上调用 event.preventDefault()。
延伸:本例用
img.src = this.href直接修改大图的src,属于"直接操作标签属性"的方式;在 设置节点的属性 一节里还介绍了第二种方式元素节点.setAttribute("属性名", "新的属性值"),两种方式对于src、title这类原始属性是等价的。
四、举例3:鼠标悬停时,显示二维码大图
需求分析
页面上是一个 50×50 的小二维码图标(用背景图定位实现)。鼠标悬停(onmouseover)在小图标上时,在旁边显示二维码大图;鼠标移开(onmouseout)时,大图隐藏。
代码实现
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.code {
width: 50px;
height: 50px;
}
.code a {
display: block;
width: 50px;
height: 50px;
background: url(http://img.smyhvae.com/20180127_QRcode_small.png) no-repeat -159px -51px;
position: relative;
}
.code-big {
position: absolute;
top: 10px;
left: 80px;
}
.hide {
display: none;
}
.show {
display: block;
}
</style>
<script>
window.onload = function () {
//需求:鼠标放到a链接上,显示二维码(添加show类名,去掉hide类名)
// 鼠标移开a链接,隐藏二维码(添加hide类名,去掉show类名)
//1.获取事件源和相关元素
var a = document.getElementsByTagName("a")[0];
var div = document.getElementsByClassName("code-big")[0];
//2.绑定事件
a.onmouseover = fn1; //鼠标悬停时
a.onmouseout = fn2; //鼠标离开时
//定义方法
function fn1() {
//3.书写事件驱动程序
div.className = "code-big show";
//div.className = div.className.replace("hide", "show");
}
function fn2() {
div.className = "code-big hide";
//了解,字符串操作,把字符串中的hide替换成show。
// div.className = div.className.replace("show","hide");
}
}
</script>
</head>
<body>
<div class="code">
<a href="#"></a>
<img src="http://img.smyhvae.com/2016040102.jpg" alt="" class="code-big hide"/>
</div>
</body>
</html>
代码解释
- 事件名不同:本例绑定的是鼠标事件
onmouseover(悬停)和onmouseout(移开),与前面的onclick(点击)不同,但绑定的语法完全一致:元素.事件 = 函数名。 - 两种事件驱动写法:
div.className = "code-big show"直接整体赋值类名,让元素同时拥有code-big(定位样式)和show(显示样式)两个类;被注释掉的div.className = div.className.replace("hide", "show")则是字符串替换的思路——在原类名的基础上把hide替换成show,可以作为理解className本质(就是一个字符串属性)的辅助参考。 - window.onload 的运用:本示例的
<script>写在<head>中,所以用window.onload包裹,保证脚本执行时 DOM 已经加载完毕(这也是 文档的加载 一节重点强调的写法)。
五、表单元素的属性总览
表单元素有一组专属属性,是后续四个表单例子的基础:
| 属性 | 含义 | 典型取值 |
|---|---|---|
type |
表单控件的类型 | text(文本框)、password(密码框)、checkbox(复选框)、radio(单选框)等 |
value |
控件的值 | 文本框中的内容、按钮上的文字等 |
checked |
复选框/单选框是否被选中 | true / false(布尔属性) |
selected |
下拉列表项是否被选中 | true / false |
disabled |
控件是否被禁用 | 属性出现即禁用(如 disabled="no"),属性消失即解禁 |
下面的四个例子分别演示了 disabled、value、焦点事件和 checked 的实战用法。
六、表单举例1:禁用文本框 / 解禁文本框
需求分析
页面上有一个账号输入框和两个按钮。点击"禁用"按钮,文本框变成只读、光标点不进去、无法编辑;点击"解禁"按钮,文本框恢复可编辑。
代码实现
<body>
账号: <input type="text" value="千古壹号..."/><button>禁用</button><button>解禁</button><br><br>
密码: <input type="password" value="aaabbbccc"/>
<script>
var inp = document.getElementsByTagName("input")[0];
var btn1 = document.getElementsByTagName("button")[0];
var btn2 = document.getElementsByTagName("button")[1];
btn1.onclick = function () {
inp.disabled = "no"; //禁用文本框。属性值里随便写什么字符串都行,但不能为空。
}
btn2.onclick = function () {
inp.disabled = false; //解禁文本框。让disabled属性消失即可。
// inp.removeAttribute("disabled");
}
</script>
</body>
</html>
代码解释
当文本框被禁用之后,文本框只读,不能编辑,光标点不进去。
禁用文本框的代码是:
inp.disabled = "no"; //让disabled属性出现,即可禁用
这里的关键思想是:我们的目的是让 disabled 这个属性"出现",属性一旦出现在标签上,控件即被禁用。所以属性值写什么都行——数字、任意字符串都可以,但不能写 0、不能写 false、不能为空,因为这些值在布尔语义下会被理解为"属性不存在"。一般习惯写 no。
解禁文本框的代码是:
inp.disabled = false; // 方法1:让disabled属性消失,即可解禁。
inp.removeAttribute("disabled"); //方法2:推荐
我们的目的是删除 disabled 属性,属性消失即解禁。属性值可以是 false、可以是 0。但实际开发中推荐使用方式二 removeAttribute("disabled"),语义最清晰。这与 删除节点的属性 一节讲到的 元素节点.removeAttribute(属性名) 完全对应——setAttribute 添加、getAttribute 获取、removeAttribute 删除,是操作节点属性最规范的三件套。
七、表单举例2:文本框获取焦点 / 失去焦点(京东 vs 淘宝的搜索框体验)
两种主流交互模式
细心的读者会发现,京东和淘宝的搜索框,获取焦点时的提示文字体验是不同的:
- 京东:输入框里默认有提示文字"微单相机"。获取焦点时(
onfocus),如果内容还是默认文字,就清空内容、文字变黑;失去焦点时(onblur),如果用户什么都没输入,就恢复默认提示文字、文字变灰。 - 淘宝:提示文字"电动牙刷"是一个用绝对定位单独放置的
<label>标签,叠在输入框上方。只要输入框里有用户输入的文字(触发oninput事件),label 就隐藏;文字被删空,label 就重新显示。
简言之:京东是判断输入框是否获取焦点;淘宝是判断输入框内是否有用户输入的文字。
代码实现
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
input {
width: 300px;
height: 36px;
padding-left: 5px;
color: #ccc;
}
label {
position: absolute;
top: 82px;
left: 56px;
font-size: 12px;
color: #ccc;
cursor: text;
}
.hide {
display: none;
}
.show {
display: block;
}
</style>
</head>
<body>
京东: <input id="inp1" type="text" value="微单相机"/><br><br>
淘宝: <label for="inp2">电动牙刷</label><input id="inp2" type="text"/><br><br>
placeholder: <input type="text" placeholder="我是placeholder"/>
<script>
//需求:京东的input按钮获取焦点后,立刻删除内容。失去后光标显示文字。
var inp1 = document.getElementById("inp1");
inp1.onfocus = function () {
//判断,如果input里面的内容是"微单相机",那么把值赋值为"";
if (this.value === "微单相机") {
inp1.value = "";
inp1.style.color = "#000";
}
}
//失去焦点事件
inp1.onblur = function () {
//判断:如果input内容为空,那么把内容赋值为微单相机。
if (this.value === "") {
inp1.value = "微单相机";
inp1.style.color = "#ccc";
}
}
//需求:在input中输入文字,label标签隐藏;当里面的文字变成空字符串,label显示。
var inp2 = document.getElementById("inp2");
var lab = document.getElementsByTagName("label")[0];
//2.绑定事件(输入文字、和删除文字时,都会触动这个事件)
inp2.oninput = function () {
//判断input中的值是否为空,如果为空,那么label显示,否则隐藏。
if (this.value === "") {
lab.className = "show";
} else {
lab.className = "hide";
}
}
</script>
</body>
</html>
代码解释
京东式(焦点事件):onfocus 获取焦点时,先判断 this.value 是否还是默认文字"微单相机"——是则清空并置为黑色;onblur 失去焦点时,判断内容是否为空——为空则恢复默认文字并置为灰色。这里用到了 value 属性(输入框的内容)和 style.color(行内样式)。
淘宝式(input 事件):oninput 事件在用户输入文字和删除文字时都会触发。每次触发都判断 this.value 是否为空字符串:为空则 label 显示(className = "show"),否则隐藏(className = "hide")。label 通过绝对定位 position: absolute 叠在输入框上,配合 cursor: text 让鼠标样式像输入框。
placeholder 的局限:示例最后给出了 HTML5 的 placeholder 属性写法(<input type="text" placeholder="我是placeholder"/>),效果类似但实现成本更低。不过 placeholder 在 IE6/7/8 中不受支持,因此在做兼容老版本 IE 的页面时不建议使用,上述"京东式 + 淘宝式"的组合仍是兼容性最好的经典方案。
八、表单举例3:注册信息校验——失去焦点后高亮显示
需求分析
账号和密码两个输入框,失去焦点时判断输入内容的长度:账号或密码在 6~12 个字符之间则通过(绿色边框高亮),否则报错(红色边框高亮)。
代码实现
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.wrong {
border: 2px solid red;
}
.right {
border: 2px solid #91B81D;
}
</style>
</head>
<body>
账号:<input type="text" onblur="fn(this)"/><br><br>
密码:<input type="password" onblur="fn(this)"/>
<script>
//需求:失去焦点的时候判断input按钮中的值,如果账号或密码在6-12个字符之间通过,否则报错。
function fn(aaa){
//html中的input标签行内调用function的时候,是先通过window调用的function,所以打印this等于打印window
// console.log(this)
//只有传递的this才指的是标签本身。
// console.log(aaa)
// console.log(this.value)
if(aaa.value.length < 6 || aaa.value.length>12){
aaa.className = "wrong";
}else{
aaa.className = "right";
}
}
</script>
</body>
</html>
代码解释
本例与前面所有例子的绑定方式都不同:没有用 JS 代码获取元素并绑定事件,而是直接在 HTML 标签上行内绑定 onblur="fn(this)"。
这里有一个非常关键的 this 知识点:在 HTML 标签上行内调用函数时,是先通过 window 调用该函数的,所以如果函数内部打印 this,得到的是 window,而不是标签本身;只有显式传递进来的 this 参数(fn(this)),才指代当前标签。因此在函数内部使用参数 aaa 来操作标签:aaa.value.length 判断输入长度,不在 6~12 之间则 aaa.className = "wrong"(红色边框),否则 aaa.className = "right"(绿色边框)。
这种校验思路可以直接迁移到真实的注册表单:在 onblur 里校验单个字段、即时给出红绿反馈,比提交后统一报错体验更好。
九、表单举例4:全选和反选
需求分析
页面上是一个 4 行数据的表格,表头第一列有一个"全选"复选框(id 为 title),每行第一列各有一个复选框。需求有两条:
- 全选/反选:点击表头的复选框,下面所有行的复选框全部选中(或全部取消选中);
- 联动回写:点击任意一行的复选框后,如果下面全部都被选中了,表头的复选框自动变为选中;只要有一个没选中,表头就不选中。
代码实现
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
* {
padding: 0;
margin: 0;
}
.my-table {
width: 300px;
margin: 100px auto 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #c0c0c0;
width: 300px;
}
th,
td {
border: 1px solid #d0d0d0;
color: #404060;
padding: 10px;
}
th {
background-color: #09c;
font: bold 16px "微软雅黑";
color: #fff;
}
td {
font: 14px "微软雅黑";
}
tbody tr {
background-color: #f0f0f0;
}
tbody tr:hover {
cursor: pointer;
background-color: #fafafa;
}
</style>
<script>
window.onload = function () {
//需求1:点击上面的的input,下面全选或者反选。
//思路:获取了上面的input按钮,只需要判断,checked属性是true还是false,如果是true,下面的全部变成true;false同理。
var topInp = document.getElementById("title");
var tbody = document.getElementById("content");
var botInpArr = tbody.getElementsByTagName("input");
//绑定事件
topInp.onclick = function () {
//费劲版
// for(var i=0;i<botInpArr.length;i++){
// if(topInp.checked === true){
// botInpArr[i].checked = true;
// }else{
// botInpArr[i].checked = false;
// }
// }
//优化版(被点击的input按钮的checked属性值,应该直接作为下面的所有的input按钮的checked属性值)
for(var i=0;i<botInpArr.length;i++){
botInpArr[i].checked = this.checked;
}
}
//需求2:点击下面的的input,如果下面的全部选定了,上面的全选,否则相反。
//思路:为下面的每一个input绑定事件,每点击一次都判断所有的按钮
// checked属性值是否全部都是true,如果有一个是false,
// 那么上面的input的checked属性也是false;都是true,topInp的checked就是true;
for(var i=0;i<botInpArr.length;i++){
botInpArr[i].onclick = function () { //每一个input都要绑定事件
//开闭原则(用开关来控制)
var bool = true;
//检测每一个input的checked属性值。
for(var j=0;j<botInpArr.length;j++){
if(botInpArr[j].checked === false){
bool = false;
}
}
topInp.checked = bool;
}
}
}
</script>
</head>
<body>
<div class="my-table">
<table>
<thead>
<tr>
<th>
<input type="checkbox" id="title" />
</th>
<th>菜名</th>
<th>饭店</th>
</tr>
</thead>
<tbody id="content">
<tr>
<td>
<input type="checkbox" />
</td>
<td>菜品1</td>
<td>木屋烧烤</td>
</tr>
<tr>
<td>
<input type="checkbox" />
</td>
<td>菜品2</td>
<td>蒸菜馆</td>
</tr>
<tr>
<td>
<input type="checkbox" />
</td>
<td>菜品3</td>
<td>海底捞火锅</td>
</tr>
<tr>
<td>
<input type="checkbox" />
</td>
<td>菜品4</td>
<td>面点王</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
代码解释
需求1(全选/反选)的两种写法:代码里保留了两种实现。"费劲版"是逐个 if (topInp.checked === true) {...} else {...} 判断后再赋值;"优化版"更简洁——被点击的表头复选框的 checked 属性值,直接作为下面所有复选框的 checked 属性值:
for(var i=0;i<botInpArr.length;i++){
botInpArr[i].checked = this.checked;
}
this.checked 是布尔值,直接把 true/false 赋给每一个行内复选框,一行的代码就完成了全选/反选。
需求2(联动回写)是难点:这里用到了两次 for 循环。第一次 for 循环是因为要给每一个行内复选框都绑定事件(循环绑定);在每次点击事件触发时,第二次 for 循环遍历检查所有行内复选框的 checked 值:
for(var i=0;i<botInpArr.length;i++){
botInpArr[i].onclick = function () { //每一个input都要绑定事件
//开闭原则(用开关来控制)
var bool = true;
//检测每一个input的checked属性值。
for(var j=0;j<botInpArr.length;j++){
if(botInpArr[j].checked === false){
bool = false;
}
}
topInp.checked = bool;
}
}
这里用到了开闭原则(开关思想):先假定开关 bool = true(全部选中),只要遍历中发现任意一个复选框 checked === false,就把开关拨到 false;最后把 bool 赋值给表头复选框 topInp.checked。这样可以避免在循环中频繁直接修改表头状态,逻辑更清晰。注意 checked 是布尔属性,比较时用 === true / === false 与布尔值严格比较。
十、从基础练习到工程化:DOM 练习的进阶路径
完成本篇文章的 8 个基础练习后,可以在本仓库的「08-前端基本功:CSS 和 DOM 练习」系列中继续进阶,把零散写法沉淀为可复用的能力:
-
访问关系的函数封装:见 05-DOM操作练习:访问关系的封装.md。把
getFirstNode、getLastNode、getNextNode、getPrevNode、getEleOfIndex、getAllSiblings等"找兄弟/找子节点"的逻辑封装进tools.js工具文件,配合<script src="tools.js">引入,能让 DOM 访问代码大幅精简。 -
Tab 栏切换与"排他思想":见 04-DOM操作练习:Tab栏切换(通过className设置样式).md。Tab 切换的核心是排他思想——先把所有同类元素的
className清空("干掉 all"),再给当前元素加上高亮类("保留我一个"),配合自定义index索引属性关联上下两块内容。文中还给出了用setAttribute("index", i)写自定义属性的对比写法,以及通过函数封装让多个 Tab 栏互不干扰的改进方案。 -
innerHTML 动态创建元素:见 07-DOM操作练习:innerHTML的方式创建元素.md。对比
document.write()(会覆盖原页面、不推荐)、innerHTML +=(适合节点套节点、注意是+=不是=)与document.createElement()配合appendChild/insertBefore/removeChild等 DOM API(适合按指定数量动态生成)三种创建方式。
本篇文章的所有示例都刻意保持了"原生 JS + 直接可运行"的形态:打开浏览器新建一个 .html 文件粘贴代码即可看到效果。建议亲手把每个例子敲一遍,重点体会三个高频考点——事件三步法、for 循环批量绑定事件时的 this 用法、通过 className 切换样式的排他思想——它们会贯穿你后续所有的 DOM 开发工作。