Skip to content

2-2样式操作及案例(二) ​

本页对应 第二章WebAPIs,共 5 段视频,约 55 分钟。

学习目标 ​

  • 能使用 classList.add()、classList.remove()、classList.toggle() 对元素类名进行追加、删除和切换,并验证不影响原有类名。
  • 能基于对象数组和随机索引,更新轮播图图片 src、文字内容及背景颜色。
  • 能使用 :nth-child() 选择对应小圆点,并通过 classList.add('active') 实现高亮显示。
  • 能读写表单元素的 type、value 属性,并使用 disabled、checked 布尔属性控制按钮禁用与复选框勾选状态。
  • 能定义 data-* 自定义属性,并通过 dataset 读取其中存储的数据。

知识点 ​

先明确:间隔函数解决什么问题 ​

为什么需要: 网页里有些功能不需要用户手动触发,而是“每隔一段时间自动做一次”。笔记里举的例子是倒计时:时间要一秒一秒地减少,不能靠用户每点一次按钮才更新。
怎么想: 这类需求的关键词是“重复”和“固定间隔”。JavaScript 把这类能力做成定时器函数,笔记说明定时器有两种:间隔函数和延迟函数,本节先学间隔函数 setInterval。
怎么做: 记住 setInterval 的作用:间隔固定的时间,自动重复执行另一个函数,也叫定时器函数。
易错点: “间隔函数”不是执行一次就结束;它会在没有关闭前反复执行。笔记里的“间隔时间单位是毫秒”,不要按秒直接写 1,要写 1000 才表示 1 秒。

开启定时器:setInterval 的写法 ​

为什么需要: 只有知道开启语法,才能把“每隔一段时间自动执行”落到代码里。
怎么想: setInterval 接收两个核心信息:要重复执行的函数,以及间隔多少毫秒。笔记的示例是每秒打印一次。
怎么做: 保留笔记代码:

html
<script>
  setInterval(function () {
    console.log('我是1秒钟执行一次')
  }, 1000)
</script>

这段代码的意思是:每隔 1000 毫秒调用一次这个匿名函数。
易错点: 第一,单位是毫秒,1000 才是 1 秒;第二,第一个参数必须是函数或函数名,不是函数执行结果;第三,如果写成 repeat(),那是立刻调用并把返回值传进去,不是把函数交给定时器。

关闭定时器:clearInterval 与定时器编号 ​

为什么需要: 间隔函数会一直重复,很多场景不能让它无限跑,比如倒计时结束、页面切换、组件销毁时都要停掉。
怎么想: 要关闭某个定时器,先得“记住”它。setInterval 的返回值是一个唯一的数字,可以存到变量里,再用 clearInterval 关闭。笔记强调:需要定时器变量名来关闭,返回的是一个唯一的数字。
怎么做: 保留笔记代码:

html
<script>
  let timer = setInterval(repeat, 1000)  // 注意调用的时候直接写函数名字不需要写小括号
  console.log(timer) //  1
  let timer1 = setInterval(repeat, 1000)  // 注意调用的时候直接写函数名字不需要写小括号
  console.log(timer1) //  2
  // 2. 关闭定时器
  clearInterval(timer)

</script>

这里 timer 和 timer1 分别保存两个定时器的编号;clearInterval(timer) 只关闭 timer 对应的那个。
易错点: 笔记中的 repeat 是示意函数名,实际使用时要先定义它;传函数名时不要加小括号。还要注意:clearInterval 里放的是定时器编号,不是函数名。

调试与验证:把“看不见的重复”变成可观察 ​

为什么需要: 定时器是异步重复执行,光看代码不容易确认它有没有开启、编号是多少、关闭后有没有停。
怎么想: 转写稿里反复用“打印一下”“打开控制台”“Ctrl+S 保存”“刷新看效果”来验证。转写稿中的同音字如“卡加斯保存”“走着”“控制台”,应纠正为 Ctrl+S 保存、运行/刷新页面、打开控制台。转写稿没有直接讲 setInterval 的调试,但这种验证顺序可以迁移:先打印 timer 看返回值,再观察重复输出,最后调用 clearInterval 看是否停止。
怎么做: 可以先写 console.log(timer) 确认拿到唯一数字;再让回调里 console.log 输出内容,观察是否按间隔重复;关闭后再看输出是否停止。
易错点: 不要只看一次输出就下结论;刷新页面会重新开始定时器,观察时要分清“页面刷新”与“定时器重复”。

易错点汇总 ​

  1. 间隔时间单位是毫秒,1000 才是 1 秒。
  2. setInterval(repeat, 1000) 中传函数名不加小括号;写成 repeat() 会立刻执行。
  3. 关闭定时器要用 setInterval 返回的变量,不要重新写一个函数名去关。
  4. 多个定时器会返回不同数字,关闭哪一个就用哪一个变量。
  5. 定时器不会自动停,不需要时必须 clearInterval。

案例目标与页面结构 ​

为什么需要:静态轮播图只能手动切换,实际页面更常见“定时自动播放”。本案例要把图片、标题文字、底部背景色和指示小圆点一起联动更新。

怎么想:页面结构分两大块:.slider-wrapper 放图片,.slider-footer 放文字、小圆点和左右按钮。数据不写死在 DOM 里,而是准备一个对象数组,每个对象包含 url、title、color,切换时按索引取对象。

怎么做:先保留笔记中的完整结构,图片 alt 改为中文说明,再在脚本中准备 sliderData。

易错点:图片路径要写对;.slider-wrapper img 和 .slider-footer p 要能准确选中;不要把小圆点的 li 与图片数组顺序弄混。

准备数据与获取元素 ​

为什么需要:轮播图每次切换要同时改四处内容,如果分别写四套数据,维护困难。用对象数组可以把“一张图对应的信息”打包在一起。

怎么想:数组每一项就是一个“幻灯片对象”。索引 i 指向当前显示的对象,取 sliderData[i].url、.title、.color 即可。

怎么做:

js
const sliderData = [
  { url: './images/slider01.jpg', title: '对人类来说会不会太超前了?', color: 'rgb(100, 67, 68)' },
  // ...其余数据保留
]
const img = document.querySelector('.slider-wrapper img')
const p = document.querySelector('.slider-footer p')
const footer = document.querySelector('.slider-footer')

易错点:querySelector 只取第一个匹配元素;数组长度就是幻灯片数量,不要手写死 8。

用信号量和定时器实现自动切换 ​

为什么需要:自动轮播需要“每隔一段时间换下一张”。定时器负责节奏,信号量 i 负责当前索引。

怎么想:声明 let i = 0,每次定时器执行先 i++,再用新索引取对象。转写稿中提到的“信号量”就是这里的索引变量。

怎么做:

js
let i = 0
setInterval(function () {
  i++
  i = i % sliderData.length
  console.log(i)
  img.src = sliderData[i].url
  p.innerText = sliderData[i].title
  footer.style.backgroundColor = sliderData[i].color
}, 1000)

易错点:i 要用 let,否则不能重新赋值;调试时先 console.log(i) 看索引是否按 0、1、2... 循环;innerText 用于纯文字标题。

取余实现无缝衔接 ​

为什么需要:播放到最后一张后,如果继续 i++ 会得到 8,而数组最大索引是 7,会取到 undefined。

怎么想:可以用 if (i >= sliderData.length) { i = 0 },也可以写成三元运算符,笔记最终采用取余:i = i % sliderData.length。当 i 等于数组长度时,余数回到 0。

怎么做:在 i++ 后立即执行 i = i % sliderData.length,再更新页面。

易错点:取余要放在“取数据之前”;不要写成 i % 8 写死长度,数组增减后会出错。

同步更新小圆点 active ​

为什么需要:只换图片不换小圆点,用户看不出当前是第几张。小圆点高亮需要和当前索引同步。

怎么想:当前索引 i 从 0 开始,而 CSS 的 :nth-child() 从 1 开始,所以第 i 张对应第 i + 1 个 li。先用 classList.remove('active') 移除旧高亮,再给新 li 添加 active。

怎么做:

js
document.querySelector('.slider-indicator .active').classList.remove('active')
document.querySelector(`.slider-indicator li:nth-child(${i + 1})`).classList.add('active')

易错点:顺序不能颠倒,笔记强调先移除再添加;nth-child 里的变量要用模板字符串;classList.add/remove 是方法,后面必须加小括号。转写稿中反复强调 classList 不会覆盖已有类名,这里正适合切换 active。

完整代码 ​

js
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>综合案例-定时版轮播图</title>
    <style>
      * { box-sizing: border-box; }
      .slider { width: 560px; height: 400px; overflow: hidden; }
      .slider-wrapper { width: 100%; height: 320px; }
      .slider-wrapper img { width: 100%; height: 100%; display: block; }
      .slider-footer {
        height: 80px; background-color: rgb(100, 67, 68);
        padding: 12px 12px 0 12px; position: relative;
      }
      .slider-footer .toggle { position: absolute; right: 0; top: 12px; display: flex; }
      .slider-footer .toggle button {
        margin-right: 12px; width: 28px; height: 28px; appearance: none;
        border: none; background: rgba(255, 255, 255, 0.1); color: #fff;
        border-radius: 4px; cursor: pointer;
      }
      .slider-footer .toggle button:hover { background: rgba(255, 255, 255, 0.2); }
      .slider-footer p { margin: 0; color: #fff; font-size: 18px; margin-bottom: 10px; }
      .slider-indicator {
        margin: 0; padding: 0; list-style: none; display: flex; align-items: center;
      }
      .slider-indicator li {
        width: 8px; height: 8px; margin: 4px; border-radius: 50%;
        background: #fff; opacity: 0.4; cursor: pointer;
      }
      .slider-indicator li.active { width: 12px; height: 12px; opacity: 1; }
    </style>
  </head>
  <body>
    <div class="slider">
      <div class="slider-wrapper">
        <img src="./images/slider01.jpg" alt="轮播图图片" />
      </div>
      <div class="slider-footer">
        <p>对人类来说会不会太超前了?</p>
        <ul class="slider-indicator">
          <li class="active"></li><li></li><li></li><li></li>
          <li></li><li></li><li></li><li></li>
        </ul>
        <div class="toggle">
          <button class="prev">&lt;</button>
          <button class="next">&gt;</button>
        </div>
      </div>
    </div>
    <script>
      const sliderData = [
        { url: './images/slider01.jpg', title: '对人类来说会不会太超前了?', color: 'rgb(100, 67, 68)' },
        { url: './images/slider02.jpg', title: '开启剑与雪的黑暗传说!', color: 'rgb(43, 35, 26)' },
        { url: './images/slider03.jpg', title: '真正的jo厨出现了!', color: 'rgb(36, 31, 33)' },
        { url: './images/slider04.jpg', title: '李玉刚:让世界通过B站看到东方大国文化', color: 'rgb(139, 98, 66)' },
        { url: './images/slider05.jpg', title: '快来分享你的寒假日常吧~', color: 'rgb(67, 90, 92)' },
        { url: './images/slider06.jpg', title: '哔哩哔哩小年YEAH', color: 'rgb(166, 131, 143)' },
        { url: './images/slider07.jpg', title: '一站式解决你的电脑配置问题!!!', color: 'rgb(53, 29, 25)' },
        { url: './images/slider08.jpg', title: '谁不想和小猫咪贴贴呢!', color: 'rgb(99, 72, 114)' },
      ]
      const img = document.querySelector('.slider-wrapper img')
      const p = document.querySelector('.slider-footer p')
      const footer = document.querySelector('.slider-footer')

      let i = 0
      setInterval(function () {
        i++
        i = i % sliderData.length
        console.log(i)

        img.src = sliderData[i].url
        p.innerText = sliderData[i].title
        footer.style.backgroundColor = sliderData[i].color

        document.querySelector('.slider-indicator .active').classList.remove('active')
        document.querySelector(`.slider-indicator li:nth-child(${i + 1})`).classList.add('active')
      }, 1000)
    </script>
  </body>
</html>

课件图示 ​

课件第64页:五、定时器-间隔函数 l 网页中经常会需要一种功能:每隔一段时间需要自动执行一段

课件第65页:五、定时器-间隔函数 定时器函数可以开启和关闭定时器 1. 开启定时器 Ø 作用

课件第66页:五、定时器-间隔函数 定时器函数可以开启和关闭定时器 2. 关闭定时器 一般不会

课件第67页:总结 总结 1. 定时器间隔函数有什么作用? Ø可以每隔指定时间自动重复执行某些

课件第68页:描述 属性/方法 效果 获取DOM对象 document.querySelect

课件第70页:案例 轮播图定时器版 效果需求: ①:每隔一秒钟自动切换图片、文本、颜色、小圆点

课件第71页:案例 轮播图定时器版 需求:每隔一秒钟切换一个图片 分析:和随机版本轮播图最大区

课件第10页:Web APIs 课程安排 7天 第四天 DOM-节点操作 节点的增删改查 第五

代码示例 ​

示例 1:间隔函数 setInterval 的开启与关闭 ​

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>间隔函数 setInterval</title>
  </head>
  <body>
    <script>
      // 1. 开启定时器:每隔 1000 毫秒(1 秒)自动执行一次函数
      let n = 0
      let timer = setInterval(function () {
        n++
        console.log('我是1秒钟执行一次', n)
        // 执行 5 次后自己把自己关掉
        if (n >= 5) {
          clearInterval(timer)
        }
      }, 1000)

      // 2. 具名函数写法:调用时直接写函数名字,不要写小括号
      function repeat() {
        console.log('repeat 被调用了')
      }
      let timer1 = setInterval(repeat, 1000)

      // 定时器返回的是一个唯一的数字
      console.log(timer)   // 1
      console.log(timer1)  // 2

      // 3. 关闭定时器:需要定时器变量名
      clearInterval(timer1)
    </script>
  </body>
</html>

要点解释

  • setInterval(函数, 间隔时间) 中,间隔时间的单位是毫秒,1000 就是 1 秒;不写时间间隔或写错单位,会导致“看起来没执行”或“执行太快”。
  • setInterval(repeat, 1000) 里传的是函数名,不能写成 repeat()。写成 repeat() 是立即调用函数并把返回值传进去,定时器就失效了。
  • setInterval 的返回值是一个唯一的数字(本例依次是 1、2),关闭定时器必须靠这个数字:clearInterval(timer)。
  • 易错点:clearInterval 里要放定时器变量名,不是放函数名;setInterval 只执行一次就不再执行,通常是没写间隔时间或函数名写错。
  • setInterval 不会自动停止,不用了要手动 clearInterval,否则会一直执行。

示例 2:综合案例——轮播图定时版(图片路径需自备 images 文件夹) ​

js
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>综合案例-定时版轮播图</title>
    <style>
      * {
        box-sizing: border-box;
      }
      .slider {
        width: 560px;
        height: 400px;
        overflow: hidden;
      }
      .slider-wrapper {
        width: 100%;
        height: 320px;
      }
      .slider-wrapper img {
        width: 100%;
        height: 100%;
        display: block;
      }
      .slider-footer {
        height: 80px;
        background-color: rgb(100, 67, 68);
        padding: 12px 12px 0 12px;
        position: relative;
      }
      .slider-footer .toggle {
        position: absolute;
        right: 0;
        top: 12px;
        display: flex;
      }
      .slider-footer .toggle button {
        margin-right: 12px;
        width: 28px;
        height: 28px;
        appearance: none;
        border: none;
        background: rgba(255, 255, 255, 0.1);
        color: #fff;
        border-radius: 4px;
        cursor: pointer;
      }
      .slider-footer .toggle button:hover {
        background: rgba(255, 255, 255, 0.2);
      }
      .slider-footer p {
        margin: 0;
        color: #fff;
        font-size: 18px;
        margin-bottom: 10px;
      }
      .slider-indicator {
        margin: 0;
        padding: 0;
        list-style: none;
        display: flex;
        align-items: center;
      }
      .slider-indicator li {
        width: 8px;
        height: 8px;
        margin: 4px;
        border-radius: 50%;
        background: #fff;
        opacity: 0.4;
        cursor: pointer;
      }
      .slider-indicator li.active {
        width: 12px;
        height: 12px;
        opacity: 1;
      }
    </style>
  </head>
  <body>
    <div class="slider">
      <div class="slider-wrapper">
        <img src="./images/slider01.jpg" alt="" />
      </div>
      <div class="slider-footer">
        <p>对人类来说会不会太超前了?</p>
        <ul class="slider-indicator">
          <li class="active"></li>
          <li></li>
          <li></li>
          <li></li>
          <li></li>
          <li></li>
          <li></li>
          <li></li>
        </ul>
        <div class="toggle">
          <button class="prev">&lt;</button>
          <button class="next">&gt;</button>
        </div>
      </div>
    </div>
    <script>
      // 初始数据
      const sliderData = [
        { url: './images/slider01.jpg', title: '对人类来说会不会太超前了?', color: 'rgb(100, 67, 68)' },
        { url: './images/slider02.jpg', title: '开启剑与雪的黑暗传说!', color: 'rgb(43, 35, 26)' },
        { url: './images/slider03.jpg', title: '真正的jo厨出现了!', color: 'rgb(36, 31, 33)' },
        { url: './images/slider04.jpg', title: '李玉刚:让世界通过B站看到东方大国文化', color: 'rgb(139, 98, 66)' },
        { url: './images/slider05.jpg', title: '快来分享你的寒假日常吧~', color: 'rgb(67, 90, 92)' },
        { url: './images/slider06.jpg', title: '哔哩哔哩小年YEAH', color: 'rgb(166, 131, 143)' },
        { url: './images/slider07.jpg', title: '一站式解决你的电脑配置问题!!!', color: 'rgb(53, 29, 25)' },
        { url: './images/slider08.jpg', title: '谁不想和小猫咪贴贴呢!', color: 'rgb(99, 72, 114)' },
      ]
      // 获取元素
      const img = document.querySelector('.slider-wrapper img')
      const p = document.querySelector('.slider-footer p')
      const footer = document.querySelector('.slider-footer')

      // 1. 声明1个变量,利用定时器实现变量++,依次拿到下一个数组中的对象
      // 1.1 声明变量 i
      let i = 0 // 信号量

      // 1.2 开启定时器实现 i++
      setInterval(function () {
        i++
        // 无缝衔接 如果播放到了最后一张 第8张,则快速复原为第1张播放
        // 其实这个8就是数组的长度
        // if (i >= sliderData.length) {
        //   i = 0
        // }

        // 改为三元运算符的写法
        // i = i >= sliderData.length ? 0 : i

        // 利用取余来实现
        i = i % sliderData.length

        console.log(i)
        // 1.3 依次拿到下一个数组中的对象
        // console.log(sliderData[i])

        // 2. 利用对象更换图片、文字、背景颜色、小圆点
        // 2.1 更换图片
        img.src = sliderData[i].url

        // 2.2 更换文字内容
        p.innerText = sliderData[i].title

        // 2.3 更换背景颜色
        footer.style.backgroundColor = sliderData[i].color

        // 2.4 更换小圆点
        // 先把以前的 active 类 移除掉  顺序不能颠倒
        document.querySelector('.slider-indicator .active').classList.remove('active')

        // 添加小圆点
        document.querySelector(`.slider-indicator li:nth-child(${i + 1})`).classList.add('active')
      }, 1000)
    </script>
  </body>
</html>

要点解释

  • 核心思路:用 setInterval 让信号量 i 每秒自增,再用 i 去数组 sliderData 里取对象,一个对象同时提供 url、title、color 三份数据,一次替换三处内容。
  • i = i % sliderData.length 是实现无缝衔接的关键行:走到第 8 张后 8 % 8 === 0,自动回到第 1 张。易错点是把 i >= length 的判断写在 i++ 之后却忘了复位,导致 sliderData[i] 越界拿到 undefined,图片变成空白。
  • 小圆点要用 `li:nth-child(\${i + 1})`,因为 i 是从 0 开始的索引,而 nth-child 从 1 开始计数,少加 1 就会整体错位一格。
  • 易错点:classList.remove('active') 必须写在 classList.add('active') 之前,顺序颠倒会出现两个小圆点同时高亮;并且选择器 .slider-indicator .active 在页面上必须真的存在一个 active 元素,否则 document.querySelector(...) 返回 null,再调 .classList 会报错。
  • 换背景色用 footer.style.backgroundColor,属性名是驼峰写法,不能写成 style.background-color。
  • 案例依赖 ./images/slider01.jpg ~ slider08.jpg 这 8 张图片,缺少图片时页面只有文字和小圆点在切换。

示例 3:classList 追加 / 删除 / 切换类名 ​

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>classList 操作元素样式</title>
    <style>
      .box {
        width: 200px;
        height: 200px;
        background-color: pink;
      }
      .circle {
        border-radius: 50%;
      }
    </style>
  </head>
  <body>
    <div class="box">box</div>
    <script>
      const box = document.querySelector('.box')

      // 1. 追加类名:不影响以前的类名
      box.classList.add('circle')

      // 2. 删除类名:只删除指定的类名,其他类名不受影响
      // box.classList.remove('box')

      // 3. 切换类名:原来有就删掉,原来没有就加上
      // box.classList.toggle('circle')
    </script>
  </body>
</html>

要点解释

  • classList 的三个方法 add、remove、toggle 都是方法,后面一定要加小括号;写成 box.classList.add 而不调用,什么效果都不会发生。
  • 参数是字符串形式的类名,类名前面不加点,写 add('circle') 而不是 add('.circle')。
  • classList.add 是追加,不会覆盖元素原有的 box 类名——这正是它比 className 好用的地方,className = 'circle' 会把原来的类名整个覆盖掉。
  • classList.toggle 是“有则删、无则加”,做开关效果(如示例 2 里小圆点高亮切换)时非常方便。
  • 修改的样式很少时用 style;样式较多、已在 CSS 中定义好类时,用 classList 添加类名。

常见错误 ​

  1. 定时器一开就"疯跑",页面卡死

    • 现象:setInterval(repeat(), 1000) 里函数只执行了一次,或控制台瞬间刷屏。
    • 原因:repeat() 带小括号是立即调用,把返回值(undefined)交给了 setInterval。
    • 修法:传函数名本身 setInterval(repeat, 1000),或用 setInterval(function () { ... }, 1000) 包装。
  2. 轮播到最后一张就报 Cannot read properties of undefined

    • 现象:能正常播 8 张,第 9 秒报错并卡住。
    • 原因:信号量 i 只做 i++,越过了数组最大索引。
    • 修法:每次自增后立即收敛,i = i % sliderData.length(或 i = i >= arr.length ? 0 : i),且这一行必须写在"取数据"之前。
  3. 关不掉的定时器

    • 现象:调了 clearInterval(timer) 但动画还在跑;或点击"暂停"毫无反应。
    • 原因:① 关闭时传的是函数名而不是 id;② 定时器 id 存在函数内部的局部变量里,外部拿不到;③ 每次开启都重新 let timer,旧 id 被覆盖。
    • 修法:let timer = null 声明在最外层作用域,开启时 timer = setInterval(...),关闭时 clearInterval(timer)。
  4. 点两次"开始"后速度翻倍

    • 现象:反复点开始按钮,轮播越来越快,甚至出现残影。
    • 原因:每点一次就新建一个定时器,多个定时器同时在跑。
    • 修法:开启前先 clearInterval(timer)(先清后开),保证同一时刻只有一个定时器存活。
  5. 小圆点出现"两个高亮"或直接报 null

    • 现象:切换后同时亮两个点;或首次加载控制台报 Cannot read properties of null (reading 'classList')。
    • 原因:① 先 add 后 remove,或只加不减;② 页面初始 HTML 里没有 .active,querySelector('.active') 返回 null。
    • 修法:严格"先移除旧的、再添加新的";并保证 HTML 中第一个圆点默认带 active,或移除前加空值判断。
  6. 用 className 加高亮,把原类名冲掉了

    • 现象:圆点加了 active 后原有样式全没了。
    • 原因:className 是整体赋值,会覆盖原有类名。
    • 修法:改用 classList.add('active') / remove / toggle,注意这三个是方法,必须带小括号。
  7. 复选框/按钮的"开关"失效

    • 现象:写了 btn.disabled = 'false',按钮仍然是禁用状态。
    • 原因:布尔属性赋了字符串,非空字符串在判断时被视为真。
    • 修法:只传布尔值 true / false,不加引号。
  8. 读取用户输入拿到空值或 undefined

    • 现象:想拿到输入框里用户打的字,结果打印出空字符串或 undefined。
    • 原因:用了 innerText / innerHTML,或选择器 [name="username"] 的值与 HTML 中 name 不一致。
    • 修法:表单取值统一用 .value;选择器先单独打印元素确认选对了再取属性。
  9. dataset 取不到值

    • 现象:data-my-name="box",写 box.dataset['my-name'] 或 box.dataset.my-name 都是 undefined 或报错。
    • 原因:data- 后面的连字符命名会被转成驼峰。
    • 修法:box.dataset.myName。

练习 ​

  1. 可控倒计时:页面上放"开始 / 暂停 / 重置"三个按钮和一个显示秒数的 div。要求:开始后每秒 +1;暂停后数字定格;重置回 0。反复快速点"开始"不能出现加速。(提示:先想清楚 timer 变量应该声明在哪一层。)

  2. 轮播图加暂停键:在综合案例的轮播图上,把底部 .toggle 的两个按钮改造成"暂停 / 播放"功能,暂停时当前图片、文字、背景色、小圆点全部定格,恢复播放时从定格的那一张继续。

  3. 随机轮播图加手动切换:在随机版轮播图的基础上,保留刷新随机显示,同时用 setInterval 每 2 秒自动切下一张,并让左右箭头能手动上一张 / 下一张。要求手动点击后小圆点、图片、文字、背景色四者永远同步。

  4. 密码显隐:输入框 type 默认为 password,旁边一个"眼睛"按钮,点击在 password / text 之间切换,按钮文字同步在"👁 / 🙈"之间切换;再要求用 disabled 控制:密码框为空时按钮不可点。

  5. 待办列表删除:用循环渲染 4 条待办,每条后面跟一个"删除"链接,用 data-index 存下标。点击任意一条只删掉它自己,其余不动。(提示:渲染时给每个链接写上 data-index="\${i}",事件里用 dataset.index 拿到下标。)

原视频对照 ​

#视频时长
1通过classList操作元素样式07:02
2随机轮播图案例-更换图片12:23
3随机轮播图案例-更换文字颜色和小圆点13:25
4操作表单常见属性14:47
5自定义属性07:46

小结 ​

  1. 定时器的一切坑,本质都是**"谁持有 id、谁负责清"**没想清楚——先定作用域,再写代码。
  2. setInterval 的回调里只做"取数据 → 渲染"两件事,把索引收敛(取余)放在渲染之前,是轮播类需求的固定套路。
  3. 涉及"索引 ↔ 页面元素"的对应关系时,nth-child(i + 1) 的 +1 不是技巧,而是"索引从 0 开始、孩子从 1 开始"这一事实的必然结果。
  4. 状态切换类操作(高亮、显隐、勾选)遵循"先清后设":旧状态先移除,新状态再添加,否则一定会出现"两个都亮"或"一个都不亮"。
  5. classList 优于 className,className 优于 style——样式少用 style,样式多用类,且类名操作一律走 classList。
  6. 表单元素的布尔属性(disabled / checked / selected)只认布尔值,任何字符串都会被当成"真",这是最隐蔽也最常见的坑。
  7. 自定义属性是"把数据挂在元素上"的官方方案,配合事件里的 dataset 取值,可以让"点哪个就操作哪个"不再依赖闭包或全局变量。
  8. 三个操作元素样式的入口各有其位:改一两句 → style;改一大片 → 预定义类 + classList;写代码前先判断属于哪一种,比事后调试省时间。