千古前端图文教程:DOM操作基础练习——事件驱动、className切换与表单属性实战

原创2026-09-30 14:24:051,438 阅读
文章标签:文档教程前端

千古前端图文教程: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 临时占位(例如在链接中指定尺寸参数即可生成对应大小的占位图),好处是素材做出来之前先留出空位,方便以后换图。

页面需求有两条:

  1. 点击小图片,改变下方大图片的 src 属性值,让其赋值为 a 链接中的 href 属性值;
  2. 让 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),每行第一列各有一个复选框。需求有两条:

  1. 全选/反选:点击表头的复选框,下面所有行的复选框全部选中(或全部取消选中);
  2. 联动回写:点击任意一行的复选框后,如果下面全部都被选中了,表头的复选框自动变为选中;只要有一个没选中,表头就不选中。

代码实现

<!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 练习」系列中继续进阶,把零散写法沉淀为可复用的能力:

  1. 访问关系的函数封装:见 05-DOM操作练习:访问关系的封装.md。把 getFirstNode、getLastNode、getNextNode、getPrevNode、getEleOfIndex、getAllSiblings 等"找兄弟/找子节点"的逻辑封装进 tools.js 工具文件,配合 <script src="tools.js"> 引入,能让 DOM 访问代码大幅精简。

  2. Tab 栏切换与"排他思想":见 04-DOM操作练习:Tab栏切换(通过className设置样式).md。Tab 切换的核心是排他思想——先把所有同类元素的 className 清空("干掉 all"),再给当前元素加上高亮类("保留我一个"),配合自定义 index 索引属性关联上下两块内容。文中还给出了用 setAttribute("index", i) 写自定义属性的对比写法,以及通过函数封装让多个 Tab 栏互不干扰的改进方案。

  3. innerHTML 动态创建元素:见 07-DOM操作练习:innerHTML的方式创建元素.md。对比 document.write()(会覆盖原页面、不推荐)、innerHTML +=(适合节点套节点、注意是 += 不是 =)与 document.createElement() 配合 appendChild / insertBefore / removeChild 等 DOM API(适合按指定数量动态生成)三种创建方式。

本篇文章的所有示例都刻意保持了"原生 JS + 直接可运行"的形态:打开浏览器新建一个 .html 文件粘贴代码即可看到效果。建议亲手把每个例子敲一遍,重点体会三个高频考点——事件三步法、for 循环批量绑定事件时的 this 用法、通过 className 切换样式的排他思想——它们会贯穿你后续所有的 DOM 开发工作。

登录后查看全文
Web