主题
读写元素的文本内容(可读可写)与读取元素的尺寸(只读)(Day13 1/3)
本页对应 Day13_DOM,共 6 段视频,约 1 小时 28 分。
学习目标
- 能使用 DOM 获取元素并操作复选框的
checked属性,独立完成反选、全选与取消全选案例。 - 能结合
Date对象和setInterval编写倒计时功能,并在倒计时结束时清除定时器、停止更新。 - 能准确区分
innerHTML、outerHTML、innerText、textContent的读取差异,并使用它们读写元素文本或 HTML 内容。 - 能区分
offset、client、scroll系列尺寸属性的含义,读取元素总宽高、内容加内边距宽高及包含溢出部分的宽高。 - 能使用
getBoundingClientRect()获取元素尺寸信息,并说明其宽高与offsetWidth、offsetHeight的对应关系。
知识点
一、先理清 DOM 的知识骨架
本节先回顾 DOM 的五类节点:document、元素、属性、文本、注释。实际开发主要用 document 和元素节点;属性节点没必要手动创建,文本和注释节点也很少直接操作。为什么需要:知道节点类型,才能判断拿到的是元素还是集合。怎么想:记住 nodeType:元素是 1,属性是 2,文本是 3,注释是 8,文档是 9。怎么做:元素节点的 nodeName 可读到标签名。易错点:不要背全部 11 种,HTML 里重点关注这 5 种即可。
获取元素要分清“单个元素”和“集合”。getElementById 返回元素或 null;getElementsByTagName、getElementsByClassName 返回 HTMLCollection;querySelector 返回第一个匹配元素,querySelectorAll 返回 NodeList。为什么需要:后面所有操作都建立在“先拿到元素”上。怎么想:优先用 CSS 选择器,范围清楚、写法直接。怎么做:
js
document.getElementById('box');
document.querySelector('#box');
document.querySelectorAll('#list input');易错点:HTMLCollection 没有 forEach,NodeList 才有;集合不能直接当元素操作,要遍历或取索引。调试时若发现“压根没获取到”,先检查选择器是否漏了 #,转写稿中的全选案例就出现过这个问题。
二、用文档结构关系找元素
已知某个元素后,可以通过关系找它的子元素、父元素、兄弟元素。为什么需要:有时先拿到容器,再操作内部元素更方便。怎么想:只操作元素,不用文本节点。怎么做:重点记 children、firstElementChild、lastElementChild、parentElement、previousElementSibling、nextElementSibling。易错点:childNodes 会包含文本和注释,课堂明确不建议拿它做元素操作;这些关系属性不带括号,是属性不是方法。
三、属性操作与样式操作
内置属性可直接“元素对象.属性名”读写;setAttribute/getAttribute 不区分内置和自定义;data-* 用 dataset 读写,短横线会自动映射成小驼峰。为什么需要:不同属性来源要用不同读写方式。怎么想:内置属性优先点语法,自定义数据优先 dataset。怎么做:
js
box.dataset.userName;
box.classList.add('active');
box.classList.remove('active');
box.classList.toggle('active');
box.classList.contains('active');样式上,style 读写行内样式;读取计算样式用 getComputedStyle(element);改类名用 className 或更推荐的 classList。易错点:style 只能读到行内样式,没写行内时得到空字符串;class 在 JS 中写作 className。
四、反选与全选案例
反选的核心是把每个复选框的 checked 取反。为什么需要:练习集合遍历和布尔值操作。怎么想:querySelectorAll 得到 NodeList,可直接 forEach。怎么做:
js
checkItems.forEach(function (check) {
check.checked = !check.checked;
});全选则是把全选框的 checked 赋给每个子复选框:
js
checkItems.forEach(function (item) {
item.checked = checkAll.checked;
});易错点:HTMLCollection 不能直接 forEach;全选案例中 ID 选择器漏写 # 会导致元素获取失败。
五、倒计时案例:先算差,再换算,再补零
倒计时要先创建目标日期和当前日期,用时间戳相减得到毫秒差,再换算天、时、分、秒。为什么需要:练习日期、取整、取余和定时器。怎么想:先取整天,再取不足一天的毫秒;先取整小时,再取不足一小时的毫秒;分钟可合并取余。怎么做:
js
var days = Math.floor(seconds / (24 * 3600 * 1000));
var hours = Math.floor(seconds % (24 * 3600 * 1000) / (3600 * 1000));
var minutes = Math.floor(seconds % (3600 * 1000) / (60 * 1000));
var secs = Math.floor(seconds % (60 * 1000) / 1000);补零可用三元表达式;定时器前要先手动调用一次计算函数,否则第一秒是空的。结束时用 clearInterval(intervalId); return;。易错点:目标日期已过会出现负数;忘记 return 当前回调还会继续执行;补零忘了真正赋值。
六、读写文本内容与读取尺寸
innerHTML 读写内部 HTML,outerHTML 包含元素自身;innerText、textContent 会去掉标签,设置时把标签当文本。为什么需要:不同场景对“是否解析标签”要求不同。怎么想:要解析标签用 innerHTML,只要文本用 innerText/textContent。易错点:outerHTML 设置时会把元素自身替换掉。
尺寸方面:offsetWidth/offsetHeight 是总宽高;clientWidth/clientHeight 是内容加内边距、不含边框;scrollWidth/scrollHeight 会把溢出部分算进去;getBoundingClientRect() 返回包含尺寸和位置的对象。易错点:这些尺寸属性只能读,不能直接写;client 不算边框,scroll 要结合溢出内容理解。
回顾清单
text
1. 节点
document、元素、属性、文本、注释
2. 获取元素
① 通过ID document.getElementById()
② 通过标签名 document.getElementsByTagName() / 元素..getElementsByTagName()
③ 通过类名
③ 通过name属性
⑤ 使用CSS选择器 querySelector() / querySelectorAll(); 文档、元素都有这两个方法
④ 快捷方法 document.documentElement、document.body、body.head、document.all
3. 文档结构 根据关系获取元素
children
firstElementChild
lastElementChild
parentElement
previousElementSibling
nextElementSibling
4. 元素的属性操作
4.1 读写内置属性 元素对象.属性名
4.2 元素对象.setAttribute() / 元素对象.getAttribute()
4.3 读写data-形式的自定义属性 元素对象.dataset.属性名
5. 元素的样式操作
5.1 读写行内样式 元素对象.style.属性名
5.2 读取计算样式 getComputedStyle(元素对象).属性名
5.2 操作类名
元素对象.className
元素对象.classList add() remove() toggle() contains()节点类型:先认识“节点”,再谈操作
为什么需要:DOM 操作的对象不是“字符串”,而是节点。知道节点类型,才能判断拿到的是元素、文本还是注释,避免把文本节点当元素操作。
怎么想:HTML 里常见的是 document、元素、属性、文本、注释五类。转写稿强调:实际开发主要用 document 和元素,属性节点、文本节点、注释节点一般不主动创建。每个节点都有 nodeName、nodeValue、nodeType;重点记 nodeType:元素 1、属性 2、文本 3、注释 8、文档 9。元素节点的 nodeName 可读到标签名,有些场景有用。
怎么做:需要判断节点时看 nodeType;需要标签名时看元素节点的 nodeName。注意完整 DOM 有 11 种节点,但 HTML 里重点关注这 5 种即可。
易错点:不要把注释节点、文本节点当元素去点 style、children;不要死记 nodeValue 的所有差异,先记 nodeType 和元素 nodeName 的用途。
获取元素:六种方式与范围差异
为什么需要:JS 要先“拿到元素对象”,才能改内容、属性、样式。拿不到元素,后面全是空操作。
怎么想:按“精确度、范围、返回值”选方法。ID 最精确;标签名、类名、name 返回集合;CSS 选择器最灵活;快捷属性适合 document、body、head、all。
怎么做:document.getElementById() 返回单个元素或 null;getElementsByTagName() 文档和元素都可调用,返回 HTMLCollection;类名、name 属性了解即可;querySelector() 返回第一个匹配元素,querySelectorAll() 返回 NodeList,文档和元素都有这两个方法。快捷方式:document.documentElement、document.body、document.head、document.all。document.all 是历史语法糖,在 if 中会被特殊处理成 false,不要依赖;ID 同名变量自动生成也不建议用。
易错点:转写稿里的调试很典型——querySelector('#id') 忘了写 #,结果拿不到元素;集合不能直接当元素操作,要索引或遍历;HTMLCollection 通常没有 forEach,NodeList 才有;getElementById 找不到是 null,不是空元素。
文档结构:根据关系拿元素
为什么需要:已经拿到某个元素后,经常要找它的子元素、父元素、兄弟元素,比如列表项联动、删除当前项。
怎么想:只操作“元素”,不操作文本和注释。所以优先用带 Element 的属性。children 拿所有子元素;childNodes 会包含文本和注释,实际很少操作。
怎么做:children、firstElementChild、lastElementChild、parentElement、previousElementSibling、nextElementSibling。它们都是属性,不是方法,使用时不要加小括号。
易错点:firstChild 可能是文本节点;children 是集合,要取第几个用索引;兄弟属性只找元素兄弟,跳过文本和注释。
属性操作:内置属性、Attribute、dataset
为什么需要:元素的状态、链接、图片地址、自定义数据都要通过属性读写。
怎么想:内置属性直接点;通用读写用 get/setAttribute;data-* 自定义属性用 dataset,最规范。
怎么做:内置属性如 img.src、input.value 直接读写;setAttribute()/getAttribute() 不区分内置和自定义;data-* 用 元素.dataset.属性名,HTML 中短横线会映射成小驼峰,JS 里不写 data-。
易错点:src、href 等会做特殊处理;布尔属性会映射成布尔值;宽高可能变数字;dataset 属性名不要带 data-;className 是内置属性,但 class 在 JS 中是关键字,所以叫 className。
样式操作:行内样式、计算样式、类名
为什么需要:改样式是 DOM 操作高频需求,但直接改 style 会污染行内样式,读样式又容易读不到。
怎么想:设置优先操作类名;读行内用 style;读最终生效样式用 getComputedStyle;classList 比 className 更精细。
怎么做:元素.style.属性名 读写行内样式,短横线转小驼峰;getComputedStyle(元素).属性名 读取计算样式,是 window 方法;className 整体读写类名;classList.add/remove/toggle/contains 增删切换判断。
易错点:style 只能读行内样式,没写就是空字符串;getComputedStyle 返回只读,不能直接赋值;className 赋值会覆盖原类名;IE 的 currentStyle 不用重点记;classList 的 toggle 是有则删、无则加。
本节核心 API:先分清“内部/自身”和“HTML/文本”
本节要解决的是:如何用元素对象读写它“里面”的内容,以及读写“包括自身在内”的内容。四个属性都是元素对象的属性,不是方法,使用时直接“元素对象.属性名”。
html
元素对象.innerHTML 读写内部的html代码和文本内容
元素对象.outerHTML 读写包括元素自身在内的html代码和文本内容
元素对象.innerText 读写内部的文本内容,会剔除掉标签
元素对象.textContent 读写内部的文本内容,会剔除掉标签,读取的值保留空格为什么需要:页面上的内容既有纯文本,也有标签结构;有时要读“里面写了什么”,有时要连元素自身一起替换。怎么想:先判断范围——inner是内部,outer是包括自身;再判断类型——HTML保留/解析标签,Text只处理文本。怎么做:先获取元素,再点属性读取或赋值。易错点:这四个属性“可读可写”,但读取差异和设置差异不是一回事,尤其innerText和textContent设置时页面表现很接近。
innerHTML:读写内部的 HTML 代码和文本内容
为什么需要:当内部既有文字又有后代标签时,用innerHTML能一次性读到或写入完整结构。怎么想:只要是双标签包裹的内容都算“内部内容”,包括直接文字、ul/li/p等后代元素。怎么做:例如先获取#box,点击读取按钮后输出box.innerHTML,控制台会看到标签、换行、缩进都带着;设置时若msg里写<h1 class="item">你好</h1>,赋给box.innerHTML后,浏览器会解析成真正的h1。易错点:设置innerHTML会替换掉元素内部原有全部内容;读取时控制台保留格式,不代表页面显示也保留这些缩进。
outerHTML:读写包括元素自身在内的 HTML 代码和文本内容
为什么需要:有时不仅要读内部,还要把“当前元素自己”一起拿走或替换。怎么想:inner是“里边的”,outer是“外边的”,所以读取时会连div自身一起带出来。怎么做:输出box.outerHTML,能看到包括#box这个元素在内的完整HTML;设置box.outerHTML = msg时,连box自身都会被新内容替换。易错点:outerHTML设置后,原来的box元素可能已经不在文档里了,后续再拿旧变量操作要格外小心;它不如innerHTML常用,因为多数操作只想改内部。
innerText:读写内部文本内容,会剔除标签
为什么需要:当只想要“文本”,不想要标签结构时用它。怎么想:读取时把标签部分去掉,只保留文字;后代元素里的文字也会被取出。怎么做:读取box.innerText,li、p里的文字会出来,但标签消失;设置box.innerText = msg时,msg里的<h1>不会被解析,而是当纯文本内容放进去。易错点:读取时它不保留缩进,空格会被处理;转写稿里“inert”应纠正为innerText。设置时它与textContent在页面上往往看不出明显差别,因为页面本身不会显示多余控制空白。
textContent:读写内部文本内容,会剔除标签,读取保留空格
为什么需要:既要纯文本,又希望读取时保留原有空白和缩进。怎么想:它和innerText很像,都会剔除标签,但读取时textContent保留空格、缩进等文本节点内容。怎么做:读取box.textContent,标签被去掉,但缩进空格还在;设置box.textContent = msg同样不解析标签。易错点:转写稿里的“tex content/tax content”应纠正为textContent;不要把“读取时保留空格”误认为“设置时也会在页面上保留空格”,页面显示时控制空白通常看不出来。
调试过程:先读后设,观察四个属性的差异
演示时先写一个#box,内部放直接文字、ul/li和p,再放“读取内容”按钮。点击后在控制台分别输出四个属性:innerHTML带标签和格式,outerHTML连自身一起带出,innerText只剩文字且不保留缩进,textContent只剩文字但保留空格。调试中若报错,先检查document是否拼错、选择器是否漏写#。再做设置:定义含<h1 class="item">的msg,分别赋给四个属性,可看到innerHTML解析标签,outerHTML替换自身,innerText和textContent把标签当纯文本。这样就能把“读取差异”和“设置差异”分开理解。
为什么需要读取元素尺寸(只读)
做页面效果时,经常要知道元素现在到底多宽多高:做拖拽要限制移动范围,做溢出判断要知道内容有没有超出盒子,做弹层定位要知道元素位置和尺寸。这里的“读取”很关键:offsetWidth、clientWidth、scrollWidth 以及 getBoundingClientRect() 都是只读的,不能通过给它们赋值来改变元素大小。要改大小,仍然去改 style.width、style.height 或类名。
先想清楚盒子模型:三组宽高分别算谁
元素盒子可以粗略分成:内容、内边距、边框、外边距,内容还可能溢出。三组属性就是围绕这些部分做取舍:
offsetWidth / offsetHeight:总宽总高,包含内容、内边距、边框,不包含外边距。clientWidth / clientHeight:内容 + 内边距,不包含边框,也不包含溢出部分。scrollWidth / scrollHeight:在client的基础上,把溢出的部分也算进来;它用于判断滚动内容实际占多大。
所以不要一上来就背属性名,先问:我要的是“实际占位总宽高”,还是“可视内容区宽高”,还是“包含溢出后的宽高”。
怎么做:读取三组尺寸
本节笔记中的核心 API 如下(已纠正笔记里的拼写:offfsetWidth → offsetWidth,offsetHeihgt → offsetHeight):
js
元素对象.offsetWidth / 元素对象.offsetHeight // 获取元素的总宽总高
元素对象.clientWidth / 元素对象.clientHeight // 获取元素宽高,内容+内边距
元素对象.scrollWidth / 元素对象.scrollHeight // 获取元素宽高,client加上溢出的部分
元素对象.getBoundingClientRect() // 返回对象,对象包含元素的位置和尺寸信息
元素对象.getBoundingClientRect().width // 同 offsetWidth
元素对象.getBoundingClientRect().height // 同 offsetHeight转写稿里的调试过程很典型:先输出 offsetWidth 和 clientWidth,发现两者相差 4,检查样式后确认是左右边框各 2px,所以 offset 比 client 多了边框。随后给内部 P 设置较大宽度,让它溢出,再观察 scrollWidth 变大,说明它把溢出部分也计算在内。即使给外层设置 overflow: hidden 或 overflow: auto,scrollWidth 仍然会把溢出内容计入,只是视觉上隐藏或出现滚动条。
易错点:clientWidth 不算边框;offsetWidth 算边框但不算外边距;scrollWidth 不是“滚动条宽度”,而是包含溢出内容后的宽度。读取结果是数字,不带 px。
getBoundingClientRect:尺寸和位置一起拿
getBoundingClientRect() 返回一个对象,里面不只有尺寸,还有位置信息。笔记只列了尺寸部分:
js
元素对象.getBoundingClientRect().width // 同 offsetWidth
元素对象.getBoundingClientRect().height // 同 offsetHeight怎么想:如果只想要总宽高,用 offsetWidth/offsetHeight 更直接;如果还要知道元素相对视口的位置,就用这个方法返回的对象。易错点:width、height 是返回对象的属性,不是元素对象自己的属性;方法名较长,注意是 getBoundingClientRect,不要拼错。
获取视口尺寸:是否包含滚动条
笔记中给出席视口尺寸代码:
js
// 会包括滚动条本身的宽度
window.innerWidth
window.innerHeight
// 不会包括滚动条本身的宽度
document.documentElement.clientWidth
document.documentElement.clientHeight为什么需要:做全屏布局、弹层遮罩、响应式判断时,要知道可视区域大小。怎么想:window.innerWidth/innerHeight 是浏览器窗口内部尺寸,包含滚动条;document.documentElement.clientWidth/clientHeight 是文档根元素的客户区尺寸,不包含滚动条。易错点:笔记里 documentElenment 是拼写错误,正确是 documentElement;两者在有无滚动条时可能不相等,不能混用。
调试顺序与总结
实际调试建议按这个顺序:先确认元素已经获取到;再输出 offsetWidth/offsetHeight 看总占位;输出 clientWidth/clientHeight 看内容加内边距;制造溢出后看 scrollWidth/scrollHeight 是否变大;最后用 getBoundingClientRect() 同时看尺寸和位置。记住结论:offset 含边框,client 不含边框,scroll 含溢出;getBoundingClientRect().width/height 与 offsetWidth/offsetHeight 一致;视口尺寸注意滚动条差异。
课堂画面






代码示例
示例一:读写元素的文本内容(innerHTML / outerHTML / innerText / textContent)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>读写元素内容</title>
<style>
#box {
width: 600px;
padding: 20px;
border: 1px solid #999;
}
.item {
padding: 20px;
background: #ccc;
}
</style>
</head>
<body>
<div id="box">
这是 div 中直接包裹的文本内容
<ul>
<li>列表项一</li>
<li>列表项二</li>
<p>段落中的文字</p>
</ul>
</div>
<button id="getBtn">读取内容</button>
<button id="setBtn">设置内容</button>
<script>
var box = document.querySelector('#box');
var getBtn = document.querySelector('#getBtn');
var setBtn = document.querySelector('#setBtn');
// ---------- 读取 ----------
getBtn.onclick = function () {
console.log('innerHTML:', box.innerHTML); // 带标签、带缩进换行
console.log('outerHTML:', box.outerHTML); // 连 div 自身都带上
console.log('innerText:', box.innerText); // 剔除标签,不保留缩进
console.log('textContent:', box.textContent); // 剔除标签,保留空格换行
};
// ---------- 设置 ----------
var msg = '<h1 class="item">Hello 你好</h1>';
setBtn.onclick = function () {
box.innerHTML = msg; // 会解析标签,h1 生效,原来的内容被整体替换
// box.outerHTML = msg; // 连 box 自己都被替换掉,页面里 box 就没了
// box.innerText = msg; // 不解析标签,标签当成纯文字显示
// box.textContent = msg; // 与 innerText 设置效果一致,标签当纯文字
};
</script>
</body>
</html>关键行与易错点
- 四个属性都是 元素对象上的属性,不是方法,读写都直接用
=,不加括号。 innerHTML/outerHTML读取时保留标签和缩进换行;outerHTML多带元素自身那对标签,所以设置outerHTML会把元素本身替换掉,页面上原元素就消失了。innerText读取时剔除标签、不保留缩进空格;textContent读取时剔除标签但保留空格与换行,这是两者唯一的读差异。- 设置时的差异:
innerHTML会解析标签(<h1>真的变成大标题);innerText/textContent不解析,标签原样当文本显示,二者设置效果肉眼无差别。 - 日常写案例多用
innerHTML(纯文字也能用);只想塞纯文本时用innerText/textContent更安全。
示例二:读取元素的尺寸与位置
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>读取元素尺寸</title>
<style>
#wrapper {
width: 600px;
padding: 20px;
border: 2px solid #999;
overflow: hidden; /* 换成 auto 会出现滚动条,scrollWidth 仍包含溢出部分 */
}
#content {
width: 800px; /* 故意让子元素溢出 */
margin: 0;
padding: 20px;
}
</style>
</head>
<body>
<div id="wrapper">
<p id="content">这是 wrapper 里的一段文字,宽度故意设成 800px 造成溢出。</p>
</div>
<script>
var wrapper = document.querySelector('#wrapper');
// 总宽总高:内容 + 内边距 + 边框
console.log('offsetWidth / offsetHeight:', wrapper.offsetWidth, wrapper.offsetHeight);
// 内容 + 内边距,不含边框,不含溢出部分
console.log('clientWidth / clientHeight:', wrapper.clientWidth, wrapper.clientHeight);
// client + 溢出部分
console.log('scrollWidth / scrollHeight:', wrapper.scrollWidth, wrapper.scrollHeight);
// 返回对象,含位置和尺寸信息
var rect = wrapper.getBoundingClientRect();
console.log('rect.width / rect.height:', rect.width, rect.height); // 同 offsetWidth / offsetHeight
// 视口尺寸
console.log('含滚动条:', window.innerWidth, window.innerHeight);
console.log('不含滚动条:', document.documentElement.clientWidth, document.documentElement.clientHeight);
</script>
</body>
</html>关键行与易错点
- 尺寸属性全部只读,只能读不能赋值;得到的值是数字(不是字符串)。
offsetWidth/Height= 内容 + 内边距 + 边框,是元素在文档流中实际占的尺寸。clientWidth/Height= 内容 + 内边距,不算边框,也不算溢出;做拖动限制时常用它取"可移动范围"。scrollWidth/Height=client+ 溢出部分,无论overflow是hidden还是auto都会把溢出部分算进去;没有溢出时与client完全相等。getBoundingClientRect()是方法要加括号,返回的是对象;rect.width / rect.height永远等于offsetWidth / offsetHeight,另外还带位置信息。- 视口两组的区别:
window.innerWidth/Height包含滚动条,document.documentElement.clientWidth/Height不包含滚动条。 - 易错:
getBoundingClientRect().height的属性名是height不是offsetHeight,别把对象属性写成元素属性。
示例三:春节倒计时(定时器 + 日期差 + 结束判断)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>春节倒计时</title>
<style>
#box { font-size: 20px; color: #f60; }
</style>
</head>
<body>
<div id="box"></div>
<script>
(function () {
var box = document.querySelector('#box');
// 目标日期:2024年2月10日 0时0分0秒(月份从 0 开始,1 表示 2 月)
var destDate = new Date(2024, 1, 10);
var timerId = null;
function runTime() {
// 当前时间必须写在函数内部,每次执行都重新创建
var nowDate = new Date();
var seconds = destDate.getTime() - nowDate.getTime(); // 相差的毫秒数
// 到达或超过目标日期,结束倒计时
if (seconds <= 0) {
clearInterval(timerId);
box.innerHTML = '春节快乐';
return; // 不写 return 本次回调仍会执行完
}
// 从毫秒数中提取 天 / 时 / 分 / 秒
var d = Math.floor(seconds / (24 * 3600 * 1000));
var h = Math.floor(seconds % (24 * 3600 * 1000) / (3600 * 1000));
var i = Math.floor(seconds % (3600 * 1000) / (60 * 1000));
var s = Math.floor(seconds % (60 * 1000) / 1000);
// 个位数补零
d = d < 10 ? '0' + d : d;
h = h < 10 ? '0' + h : h;
i = i < 10 ? '0' + i : i;
s = s < 10 ? '0' + s : s;
box.innerHTML = '距离春节还有 ' + d + ' 天 ' + h + ' 小时 ' + i + ' 分 ' + s + ' 秒';
}
runTime(); // 先手动执行一次,避免第一秒是空白
timerId = setInterval(runTime, 1000);
})();
</script>
</body>
</html>关键行与易错点
- 外面套立即执行函数
(function(){ ... })(),目的是产生独立作用域,页面里多个特效时同名变量互不干扰。 new Date(2024, 1, 10)的月份从 0 开始,写1才是 2 月;后面的时分秒不写默认为 0。destDate是固定值,写在函数外面创建一次即可;nowDate必须写在函数里面,否则时间不会流逝。- 提取天数的除数是一天的毫秒数
24 * 3600 * 1000;提取小时、分钟要先%取余拿到"不足一天 / 不足一小时"的毫秒数,再除,最后Math.floor取整去掉小数。 - 分钟那步可以合并化简:
seconds % (3600 * 1000) / (60 * 1000),因为24 * 3600 * 1000本身就是3600 * 1000的整数倍。 - 先
runTime()再setInterval,否则页面第一秒是空的。 - 结束判断里
clearInterval之后必须return,否则本次回调后面的拼接代码还会继续执行完。 - 补零的三元判断在倒计时结束后要注意:负数也小于 10,会补出
0-1这种结果,所以一定要先拦截seconds <= 0。
常见错误
现象:对
getElementsByTagName()的结果直接调用forEach(),报xxx.forEach is not a function。
原因:得到的是HTMLCollection,没有forEach;querySelectorAll()得到的才是NodeList。
修法:改用for循环,或Array.from(集合).forEach(...),或统一改用querySelectorAll()。现象:
querySelector('checkAll')拿到null,后续操作报Cannot read properties of null。
原因:按 ID 获取时漏写#,选择器被当成标签名。
修法:写成querySelector('#checkAll'),单个元素也可直接用getElementById()。现象:用
setAttribute('checked', 'false')取消复选框,结果仍然选中。
原因:checked是布尔属性,setAttribute写入字符串'false'仍会被视为存在。
修法:直接赋值checkbox.checked = false,全选/反选都用布尔值。现象:倒计时数字不变化,始终显示第一次的值。
原因:new Date()和时间差计算写在了setInterval外面,只执行一次。
修法:把“当前时间 + 时间差计算”放进定时器回调内部,目标日期可放外面。现象:倒计时到 0 后继续出现负数,补零后显示成
0-1之类。
原因:没有终止定时器,也没有阻止后续渲染。
修法:判断剩余毫秒<= 0时clearInterval(timerId),并return结束本次回调。现象:补零后仍然显示
0而不是00。
原因:三元表达式只计算了结果,没有重新赋值给变量。
修法:写成d = d < 10 ? '0' + d : d;,时、分、秒同理。现象:用
innerText设置含<h1>的内容,页面上原样显示标签文本。
原因:innerText/textContent按纯文本处理,不解析 HTML。
修法:需要解析标签用innerHTML;需要纯文本才用innerText或textContent。现象:设置
outerHTML后,原来保存的元素引用再操作样式或内容无效。
原因:outerHTML连元素自身一起替换,原元素已不在 DOM 中。
修法:要保留自身就用innerHTML;确实替换自身后需重新获取元素。现象:用
el.style.width读取样式表中的宽度,得到空字符串。
原因:style只读行内样式。
修法:读取最终生效样式用getComputedStyle(el).width。现象:做拖拽或限制移动范围时用
offsetWidth,元素能拖到边框上。
原因:offsetWidth包含边框,可移动区域通常只算内容加内边距。
修法:改用clientWidth/clientHeight限制范围。
练习
实现“反选”按钮:用
querySelectorAll()获取复选框并反转每个checked;再用getElementsByTagName()获取一次,改用for循环实现,比较两种集合的遍历方式。实现全选/取消全选联动:点击全选框控制所有子复选框;手动取消任一子复选框时,全选框自动取消。要求全部使用
checked布尔赋值,不使用setAttribute。封装春节倒计时
runTime():先手动调用一次,再setInterval;对天、时、分、秒补零;剩余时间<= 0时清除定时器、return,并显示“春节快乐”,不能出现负值。写一个包含文本、子元素和空白的容器,分别输出
innerHTML、outerHTML、innerText、textContent;再用innerHTML和innerText设置同一段含<h1>的内容,记录并解释差异。给容器设置宽度、内边距、边框,并让子元素宽度溢出;输出
offsetWidth、clientWidth、scrollWidth和getBoundingClientRect().width;再把overflow分别改为hidden、auto观察结果并说明原因。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 回顾 | 15:10 |
| 2 | 反选案例 | 04:06 |
| 3 | 点击复选框全选案例 | 04:55 |
| 4 | 倒计时案例 | 32:46 |
| 5 | 读写元素中的文本内容 | 17:45 |
| 6 | 读取元素的尺寸 | 14:06 |
小结
- DOM 批量操作第一步不是遍历,而是先确认拿到的是单个元素、
NodeList还是HTMLCollection。 - 内容读写要按“是否解析标签、是否包含自身、是否保留空白”三个维度选属性,不能习惯性只用
innerHTML。 - 尺寸读取要按“含不含边框、含不含溢出”选系列;需要位置和尺寸一起取时用
getBoundingClientRect()。 - 样式读取必须区分行内样式和计算样式;批量改样式优先操作
classList,不要逐个写行内样式。 - 定时器类效果要写全“首次执行、终止条件、清理定时器”三件套,否则容易出现空窗、负值和停不下来。
checked、disabled这类布尔属性直接赋布尔值,不要用setAttribute写字符串。- 多效果页面用立即执行函数隔离作用域,变量和元素获取集中管理,避免同名污染。
- 换算类逻辑不要干想,先写起来,按“先取整、再取余、再换算”的顺序拆解,小学数学足够应付。