Skip to content

DOM 介绍与获取元素(Day12 2/3) ​

本页对应 Day12_DOM,共 7 段视频,约 1 小时 20 分。

学习目标 ​

  • 能使用 querySelector 获取第一个匹配元素、使用 querySelectorAll 获取所有匹配元素集合,并说明无匹配时分别返回 null 和空集合。
  • 能通过 document.documentElement、document.body、document.all 快捷获取特殊元素或元素集合,并利用 document.all 的布尔转换结果说明其判断旧 IE 的适用范围。
  • 能解释“JS 会自动创建与 ID 同名变量”的现象,并在代码中避免依赖该非标准写法。
  • 能使用 children、firstElementChild、lastElementChild、parentElement、previousElementSibling、nextElementSibling 根据元素关系获取子元素、父元素和兄弟元素。
  • 能区分内置属性与自定义属性,并使用“元素对象.属性名”正确读写内置属性。
  • 能使用 getAttribute 和 setAttribute 读取、设置标签代码上的属性,并说明其不区分内置与自定义属性、未写入代码时读取为 null 的规则。

知识点 ​

DOM 与 CSS 选择器获取元素 ​

本节笔记标题是“DOM 介绍”,笔记中保留了 MDN 的文档对象模型手册链接:MDN 文档对象模型手册。学习 DOM 的第一步,是先把 HTML 中的元素拿到 JavaScript 里。为什么需要?因为只有先获取到元素对象,后面才能改内容、改样式、绑事件。怎么想?转写稿里反复强调:CSS 选择器本来就是“选元素”的,而且“指哪打哪”,所以 JavaScript 也提供了用 CSS 选择器获取元素的方法。怎么做?核心是两个方法:querySelector 和 querySelectorAll。querySelector 返回符合条件的第一个元素;querySelectorAll 返回所有符合条件的元素组成的集合,通常是 NodeList。它们既可以从整个文档获取,也可以从某个元素的后代中获取。示例:

js
const bookEle = document.querySelector('#books');
console.log(bookEle);

const itemEle = document.querySelector('.item'); // 只拿第一个
const itemList = document.querySelectorAll('.item'); // 拿所有,集合

也可以写复杂选择器,例如 document.querySelector('.wrapper p:nth-child(odd)'),表示在 .wrapper 后代中找奇数位置的 p。易错点:querySelector 永远只返回第一个,不会因为你有多个就全给你;querySelectorAll 永远返回集合,即使只有一个或一个都没有,也是集合,没有匹配就是空集合。转写稿还提醒:获取到的结果无非两类——单个元素或集合;集合要遍历或用索引取。

快捷方式:documentElement、body 与 document.all ​

为什么需要?像 html、body 这种在文档中很特殊的元素,如果每次都写选择器会啰嗦。怎么想?既然它们特殊,就可以给快捷方式。怎么做?document.documentElement 获取 html 元素,document.body 获取 body 元素。转写稿中还给 html、body 加边框验证,检查元素时能看到行内样式。代码:

js
console.log(document.documentElement);
document.documentElement.style.border = '2px solid red';

console.log(document.body);
document.body.style.border = '2px solid green';

document.all 可获取文档中所有元素组成的集合。转写稿演示输出 17 个,并说明它也是集合、类数组。易错点:document.all 有一个特殊现象——它有值,但转成布尔值时是 false。转写稿用 if (document.all) 做 IE 快速判断:成立时输出 IE 浏览器,不成立时输出非 IE;并说明 IE11 在这个判断里会走到“非 IE”分支。这里要记住:这是特殊行为,不要按“有值就是 true”去推。

与 ID 同名的变量:知道即可,不建议使用 ​

为什么需要?转写稿说这是 JavaScript 的一个特殊现象:HTML 中有 ID,JavaScript 中可能自动创建与 ID 同名的变量。怎么想?这不是我们主动声明变量,而是浏览器行为。怎么做?例如页面有 id="books",不写 getElementById,直接输出 books 也可能拿到该元素。易错点:转写稿明确说不建议这么干,因为可读性不好,也不符合“变量要用时先创建”的编程习惯;知道现象即可,开发中还是老老实实获取元素。

根据文档结构获取元素:节点与元素要分清 ​

为什么需要?已经拿到某个元素后,常常还要操作它的父元素、子元素、兄弟元素;如果重新用选择器找,麻烦。怎么想?DOM 文档结构有两种表述:节点树和元素树。节点树包括文本节点、注释节点、属性节点等;元素树只考虑元素。转写稿用 children 演示:一个 div 里写了 6 个 p,但 children 若用节点方式会得到 13 个,因为 p 与 p 之间的换行也被当成文本节点。怎么做?实际开发优先用元素关系:

js
const booksEle = document.getElementById('books');
console.log(booksEle.children); // 所有子元素,HTMLCollection
console.log(booksEle.firstElementChild); // 第一个子元素
console.log(booksEle.lastElementChild);  // 最后一个子元素
console.log(booksEle.parentElement);     // 父元素
console.log(booksEle.previousElementSibling); // 上一个兄弟元素
console.log(booksEle.nextElementSibling);     // 下一个兄弟元素

易错点:children 是子元素集合,不是子节点集合;childNodes 会把换行文本也算进去。获取兄弟元素要用 previousElementSibling、nextElementSibling,不要用会拿到文本节点的旧属性。转写稿还演示了给第三个子元素设置 text-decoration,说明拿到元素后可直接操作样式。

读写内置属性:点语法映射 ​

为什么需要?HTML 标签在标准中有很多属性,例如 img 的 src、alt、width、height,以及 id、class、title 等全局属性。怎么想?元素拿到 JavaScript 中变成对象后,标准属性会映射成对象属性,所以可以用点语法读写。怎么做?

js
const imageBox = document.querySelector('img');
console.log(imageBox.src);
console.log(imageBox.alt);
console.log(imageBox.width);
imageBox.src = './images/poker.jpg';
imageBox.width += 50;

转写稿调试发现:src 写的是相对路径,读出来会被解析成绝对路径,中文还可能被编码;width 读出来是数字;没写 height 也能读到,因为标准属性存在,未写时可能是空字符串或默认值。易错点:内置属性会做特殊处理,例如 checked、selected、disabled 这类不需要写值的属性,映射后常是布尔值。复选框示例中,checkbox.checked 可读可改,甚至可写 checkbox.checked = !checkbox.checked 来切换。

读写标签代码上的属性:getAttribute 与 setAttribute ​

为什么需要?开发者可能写自定义属性,例如 loadPic="100.jpg",它不是标准内置属性,不能用点语法可靠读取。怎么想?这时要用专门操作“标签代码上属性”的方法。怎么做?

js
console.log(imageBox.getAttribute('loadPic'));
console.log(imageBox.getAttribute('src'));
imageBox.setAttribute('src', './images/new.jpg');
imageBox.setAttribute('home-address', '北京');

易错点:getAttribute 不区分内置属性和自定义属性,只看标签代码上有没有写;没写就是 null。它不会像点语法那样把 width 处理成数字,也不会把 checked 处理成布尔值。setAttribute 若属性不存在会新增到文档结构的元素上。转写稿还提到自定义属性可用来“藏数据”,淘宝等网站检查元素时能看到大量自定义属性,但具体含义只有开发者自己知道。

获取元素:先明确“单个元素”还是“集合” ​

为什么需要:DOM 操作的第一步是拿到元素,后面才能改样式、改内容、绑事件。怎么想:先判断方法返回的是单个元素、null,还是伪数组集合;集合要遍历或按索引取。怎么做:优先用 CSS 选择器方法,特殊元素用快捷方式。易错点:不要把所有获取结果都当成数组直接调用数组方法。

① 通过 ID 名 ​

为什么需要:ID 在文档中通常唯一,最适合精确拿单个元素。怎么想:getElementById 只按 ID 找,参数不要加 #。怎么做:

js
document.getElementById('ID名')
1. 返回符合条件的元素对象
2. 如果获取不到元素,返回 null

易错点:找不到时是 null,不是空集合;转写稿还提醒,浏览器会自动创建与 ID 同名的变量,但不建议依赖,可读性差且不规范。

② 通过标签名 ​

为什么需要:同一类标签可能有很多,需要批量获取。怎么想:可从 document 找全文档,也可从某个元素的后代中找。怎么做:

js
// 从文档中获取所有指定标签名的元素
document.getElementsByTagName('标签名');

// 从某个元素的后代中获取所有指定标签名的元素
元素.getElementsByTagName('标签名');
1. 返回的是一个 HTMLCollection 对象,是一个伪数组,里面的成员是元素对象
2. 如果没有符合条件的元素,同样返回 HTMLCollection 对象,是个空集合

易错点:没有匹配时仍返回空集合,不是 null;集合是伪数组,不能直接 forEach 前要先确认方法兼容性,通常可用索引或遍历。

③ 通过类名(了解,IE8+ 支持) ​

为什么需要:类名适合按样式分组批量选元素。怎么想:和标签名类似,也返回集合。怎么做:

js
// 从文档中获取所有指定类名的元素
document.getElementsByClassName('标签名');

// 从某个元素的后代中获取所有指定类名的元素
元素.getElementsByClassName('标签名');
1. 返回的是一个 HTMLCollection 对象,是一个伪数组,里面的成员是元素对象
2. 如果没有符合条件的元素,同样返回 HTMLCollection 对象,是个空集合

易错点:参数写类名本身,不要带点;笔记代码中参数文字写成“标签名”,实际应理解为类名。

④ 通过 name 属性值(了解) ​

为什么需要:表单元素常用 name 分组,需要按 name 批量取。怎么想:只有 document 才有这个方法。怎么做:

js
// 只要 document 才有 getElementsByName 方法
document.getElementsByName('name属性值');
1. 返回的是一个 NodeList 对象,是一个伪数组,里面的成员是元素对象
2. 如果没有符合条件的元素,同样返回 NodeList 对象,是个空集合

易错点:返回 NodeList,不是 HTMLCollection;没有匹配仍是空集合。

⑤ 使用 CSS 选择器获取元素(推荐) ​

为什么需要:CSS 选择器“指哪打哪”,ID、类、标签、后代、伪类都能组合。怎么想:querySelector 只要第一个,querySelectorAll 要全部。怎么做:

js
// 从整个文档中获取
document.querySelector('CSS选择器')
document.querySelectorAll('CSS选择器')

// 从指定元素的后代中获取
元素.querySelector('CSS选择器')
元素.querySelectorAll('CSS选择器')
1. querySelector() 返回符合选择器条件的第一个元素,没有符合条件的元素返回 null
2. querySelectorAll() 返回所有符合选择器条件的元素组成的集合,是 NodeList 对象,是伪数组。

转写稿调试过程:写 document.querySelector('.item') 时,即使页面有多个 item,也只拿第一个;写 document.querySelector('.wrapper .item') 可限定后代。若选择器写成 .wrapper .item.active 而页面没有匹配,querySelector 返回 null;querySelectorAll 则返回空集合。易错点:querySelectorAll 永远返回集合,哪怕只有一个或零个;集合需遍历或索引取值。

⑥ 快捷方式获取元素 ​

为什么需要:html、head、body 在文档中特殊且常用,不必每次写选择器。怎么想:浏览器提供现成属性。怎么做:

js
document.body				获取到body元素
document.head				获取到head元素
document.documentElement	获取到html元素(根元素)
document.all				获取本文档中所有的元素组成的集合

使用 document.all 判断是否是 IE 浏览器:

js
if (document.all) {
     // IE10以及以下的浏览器
     document.write('您使用的是IE浏览器!');
 } else {
     // IE11、EDGE、Chrome、Firefox、Safari 等等
     document.write('您使用的不是IE浏览器!');
 }

易错点:现代浏览器里 document.all 有集合值,但转布尔会得到 false,这是特殊行为;不要用常规“有值就是 true”推理。document.all 得到的是全部元素集合,也是伪数组。

为什么需要“根据元素关系获取元素” ​

前面学的是从文档中直接找元素,比如按 id、标签名或选择器获取。但实际开发里经常是:已经拿到某个元素,并对它操作完了,接着还要操作它的子元素、父元素或兄弟元素。这时如果重新从 document 上再查一遍,不但麻烦,还容易把范围搞大。更自然的思路是:先拿到一个基准元素,再顺着文档结构找它周围的元素。这就是“根据元素关系获取元素”。

本节要掌握的属性如下,先原样保留笔记代码:

js
children				获取所有的子元素,是一个 HTMLCollection 类型的对象
firstElementChild		获取第一个子元素
lastElementChild		获取最后一个子元素

parentElement			获取父元素

previousElementSibling	获取紧邻的前面的一个兄弟元素
nextElementSibling		获取紧邻的后面的一个兄弟元素

这里的“元素关系”指的是元素树:父元素、子元素、兄弟元素。它和“节点树”不是一回事。节点树里除了元素节点,还会有文本节点、注释节点等;元素树只关心元素。转写里特别强调:以前节点方法兼容性好,但会拿到换行、空格等文本节点,处理起来很烦;现在直接学元素关系更合理。

children:获取所有子元素 ​

为什么需要:想批量操作某个容器下面的所有子元素时,需要一个集合。
怎么想:不要用 childNodes 去拿“所有子节点”,因为换行、空格也会被算成文本节点;要拿“所有子元素”,就用 children。
怎么做:父元素.children 返回 HTMLCollection 类型对象,是伪数组,可以遍历,也可以用索引取某一个。
易错点:children 只包含元素,不包含文本节点。转写里的调试很典型:一个容器里写了 6 个 <p>,直接看 childNodes 却是 13 个,因为 <p> 之间的换行被当成了文本节点,第一个成员还是文本节点,第二个才是 <p>;换成 children 后就是 6 个,而且每个成员都是元素。另一个易错点是:想拿第二个子元素,没有专门的 secondElementChild,要用 children[1]。

firstElementChild 与 lastElementChild ​

为什么需要:经常只想操作第一个或最后一个子元素,比如给第一项加样式、给最后一项去掉边框。
怎么想:既然只要一个元素,就不要先拿集合再绕。
怎么做:父元素.firstElementChild 获取第一个子元素;父元素.lastElementChild 获取最后一个子元素。它们返回的是元素本身,不是集合。
易错点:不要和 firstChild、lastChild 混用。后者是节点方法,可能拿到换行文本。转写中第一个子节点是换行文本,拿它没有意义;所以宁可属性名长一点,也要用 firstElementChild。另外,如果容器没有子元素,这类属性会得到 null,后续操作前要判断。

parentElement:获取父元素 ​

为什么需要:拿到子元素后,有时要回头操作它的父容器,比如从某个按钮找到所在卡片。
怎么想:从当前元素往上找一层。
怎么做:元素.parentElement 获取父元素。转写里对 booksELE 取 parentElement,得到的是 body;再对结果继续 .parentElement,得到的是 html。
易错点:parentElement 只往上找一层,不是找所有祖先。到 html 再往上就是 null。不要和 parentNode 混用;本节按元素树记 parentElement 即可。

previousElementSibling 与 nextElementSibling ​

为什么需要:操作完当前元素,常常要操作它紧挨着的前一个或后一个兄弟元素。
怎么想:只找“紧邻”的兄弟元素,不是找所有兄弟。
怎么做:元素.previousElementSibling 获取前面紧邻的兄弟元素;元素.nextElementSibling 获取后面紧邻的兄弟元素。转写里验证:booksELE 前面的兄弟是 h1,后面的兄弟是 a。
易错点:这两个属性返回单个元素,不是集合;找不到时返回 null。不要用 previousSibling、nextSibling 代替,因为它们可能拿到换行文本节点。还要注意“紧邻”二字:中间隔着别的元素,拿到的就不是你想要的那个。

小结:先拿基准,再按元素树走 ​

这一节的用法可以概括为:先获取一个元素作为基准,然后往下用 children、firstElementChild、lastElementChild,往上用 parentElement,左右用 previousElementSibling、nextElementSibling。核心易错点只有一个:始终区分“节点”和“元素”。节点方法会把换行、空格算进去,元素方法只认标签元素。把这一点想清楚,文档结构获取元素就不会乱。

一、用 CSS 选择器获取元素 ​

为什么需要:getElementById、getElementsByTagName 能选元素,但表达力有限。CSS 选择器“指哪打哪”,JS 也应能直接复用。
怎么想:把 CSS 选择器字符串交给 querySelector / querySelectorAll,让浏览器按选择器规则找元素。
怎么做:

js
const bookEle = document.querySelector('#books'); // 第一个匹配元素
const firstItem = document.querySelector('.item'); // 多个.item只要第一个
const items = document.querySelectorAll('.item');  // 所有.item,NodeList
const oddP = document.querySelector('.wrapper p:nth-child(odd)');

也可以从某个元素的后代中找:

js
const p = bookEle.querySelector('p');    // books后代中第一个p
const ps = bookEle.querySelectorAll('p'); // books后代中所有p

易错点:querySelector 只返回第一个匹配元素,没有匹配返回 null;querySelectorAll 永远返回集合,没有匹配就是空集合。参数是 CSS 选择器字符串,# 表示 id,. 表示类名。

二、快捷获取与特殊现象 ​

为什么需要:html、body 太常用,每次写选择器麻烦;document.all 可一次拿到所有元素。
怎么做:

js
document.documentElement.style.border = '1px solid red';
document.body.style.border = '1px solid green';
console.log(document.all.length); // 文档中所有元素组成的集合

document.all 还可作为历史技巧判断旧 IE:if (document.all) { ... }。它不符合常规布尔转换,只作了解,新项目不要依赖。

另外,JS 会为有 id 的元素自动创建同名变量,例如 id 为 books 时可直接用 books。易错点:这不标准、可读性差,变量名写错时容易“看起来没获取却还能用”,开发中仍应老老实实 querySelector 或 getElementById。

三、按文档结构获取元素 ​

为什么需要:已拿到父元素后,常要继续操作子元素、兄弟元素,没必要重新从文档查找。
怎么想:区分“节点树”和“元素树”。childNodes 包含换行文本、注释等;children 只包含元素。开发中优先用元素树。
怎么做:

js
const books = document.querySelector('#books');

console.log(books.childNodes.length); // 含换行文本,可能为13
console.log(books.children.length);   // 只算子元素,6
console.log(books.firstElementChild);
console.log(books.lastElementChild);
console.log(books.parentElement);
console.log(books.previousElementSibling);
console.log(books.nextElementSibling);

books.children[2].style.textDecoration = 'underline wavy red';

易错点:firstChild 可能拿到换行文本,firstElementChild 才是第一个子元素;children 是 HTMLCollection,可用索引和遍历,但不是数组。

四、读写内置属性 ​

为什么需要:用 JS 换图、改宽高、控制复选框选中状态。
怎么想:HTML 标准规定的属性会映射成元素对象的属性。图片的 src、alt、width、height,表单的 checked 等,都可用“点语法”读写。
怎么做:

js
const imageBox = document.querySelector('img');

console.log(imageBox.src);    // 会被解析成绝对路径,中文可能编码
console.log(imageBox.alt);
console.log(imageBox.width);  // 数字
console.log(imageBox.height); // 未写也可能读到计算值
console.log(imageBox.id);     // 未写是空字符串

imageBox.src = './images/poker.jpg';
imageBox.width += 50;

const check = document.querySelector('#checkBook');
console.log(check.checked); // true/false
check.checked = !check.checked;

易错点:src 读取时可能被处理成绝对路径;width、height 是数字;标准属性即使没写在标签上,对象里也可能存在,只是值为空字符串或默认值;checked 这类布尔属性读出来是布尔值,不是字符串。

五、getAttribute 与 setAttribute ​

为什么需要:自定义属性(如 data-pic)没有映射成对象属性,不能用 imageBox.dataPic 读取;同时也要精确读写“标签代码上写过的属性”。
怎么想:getAttribute / setAttribute 不区分内置和自定义,只看标签上有没有写。
怎么做:

js
console.log(imageBox.getAttribute('data-pic')); // 自定义属性
imageBox.setAttribute('data-pic', '200.jpg');
imageBox.setAttribute('data-address', '...');   // 不存在会添加

console.log(imageBox.getAttribute('src'));    // 原样字符串,不解析
console.log(imageBox.getAttribute('height')); // 标签没写则 null

易错点:getAttribute 返回字符串,不做 width 数字转换,也不把 checked 转布尔;手动勾选复选框,不会让 getAttribute('checked') 变成 true。setAttribute 改的是文档结构中的元素,不是磁盘上的源码文件。自定义属性常用来藏数据,但不要滥用。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

1. CSS 选择器获取元素 + 快捷方式 + 文档结构 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>DOM 获取元素与文档结构</title>
  <style>
    .wrapper {
      margin: 20px;
      width: 600px;
      background: #ccc;
    }
    .wrapper p {
      margin: 20px;
      background: #099;
      color: #fff;
    }
  </style>
</head>
<body>
  <h1>文档结构</h1>

  <div id="books" class="wrapper">
    <p>1</p>
    <p>2</p>
    <p>3</p>
    <p>4</p>
    <p>5</p>
    <p>6</p>
  </div>

  <a href="#">链接</a>

  <script>
    // 通过 CSS 选择器获取元素
    const booksEle = document.querySelector('#books');
    console.log(booksEle);

    // querySelector 只获取符合条件的第一个元素
    const firstP = document.querySelector('.wrapper p');
    console.log(firstP);

    // querySelectorAll 获取所有符合条件的元素,返回 NodeList 伪数组
    const allP = document.querySelectorAll('.wrapper p');
    console.log(allP, allP.length);

    // 快捷方式获取元素
    console.log(document.body);
    console.log(document.head);
    console.log(document.documentElement);

    // 文档结构:根据元素关系获取元素
    console.log(booksEle.children);            // HTMLCollection,6 个 p
    console.log(booksEle.firstElementChild);   // 第一个子元素 p
    console.log(booksEle.lastElementChild);    // 最后一个子元素 p

    // 第二个子元素
    console.log(booksEle.children[1]);

    // 给第三个子元素设置下划线
    booksEle.children[2].style.textDecoration = 'underline wavy red';

    // 父元素、兄弟元素
    console.log(booksEle.parentElement);          // body
    console.log(booksEle.previousElementSibling); // h1
    console.log(booksEle.nextElementSibling);     // a
  </script>
</body>
</html>

关键行与易错点:

  • document.querySelector('CSS选择器') 返回符合条件的第一个元素;没有找到返回 null。
  • document.querySelectorAll('CSS选择器') 永远返回集合,即 NodeList 伪数组;没有找到返回空集合。
  • querySelector 参数写 CSS 选择器,例如 #books、.wrapper p;getElementById 参数不写 #。
  • children 获取所有子元素,返回 HTMLCollection 伪数组,不包含换行、空格等文本节点。
  • firstElementChild、lastElementChild、parentElement、previousElementSibling、nextElementSibling 获取的都是元素本身,不是集合。
  • 不要误用 childNodes 获取子元素,它会把换行文本也当成节点。

2. 读写内置属性 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>读写内置属性</title>
  <style>
    img {
      display: block;
      margin: 10px 0;
    }
  </style>
</head>
<body>
  <img
    id="imageBox"
    src="images/1.jpg"
    alt="小乐正在吃蛋糕"
    title="小乐正在吃蛋糕"
    width="300"
  >

  <input type="checkbox" id="checkBook" checked> 复选框

  <button id="btn01">读取属性值</button>
  <button id="btn02">换图</button>
  <button id="btn03">放大</button>
  <button id="btn04">缩小</button>
  <button id="btn05">读取/切换 checked</button>

  <script>
    const imageBox = document.querySelector('#imageBox');
    const checkBook = document.querySelector('#checkBook');

    document.querySelector('#btn01').onclick = function () {
      console.log('src:', imageBox.src);
      console.log('alt:', imageBox.alt);
      console.log('title:', imageBox.title);
      console.log('width:', imageBox.width);   // 数字
      console.log('height:', imageBox.height); // 数字,未写也能读到
      console.log('id:', imageBox.id);         // 没写就是空字符串
    };

    document.querySelector('#btn02').onclick = function () {
      imageBox.src = 'images/2.jpg';
    };

    document.querySelector('#btn03').onclick = function () {
      imageBox.width += 50;
    };

    document.querySelector('#btn04').onclick = function () {
      imageBox.width -= 50;
    };

    document.querySelector('#btn05').onclick = function () {
      console.log('切换前 checked:', checkBook.checked);
      checkBook.checked = !checkBook.checked;
    };
  </script>
</body>
</html>

关键行与易错点:

  • HTML 标签上的内置属性会映射成元素对象的属性,可以直接用 元素.属性名 读取和设置。
  • imageBox.src 读取时会解析成绝对路径,相对路径和中文可能被编码处理。
  • imageBox.width、imageBox.height 读取到的是数字;没有写 height 也能读到计算后的高度。
  • 没有写 id 时,imageBox.id 得到的是空字符串,不是 undefined。
  • checked 是布尔属性,读取到的是布尔值;用户勾选或取消会改变它。
  • checkBook.checked = !checkBook.checked 可以切换选中状态。
  • 内置属性可以直接点语法读写,但自定义属性不能这样读写。
  • 示例中的 images/1.jpg、images/2.jpg 换成自己的图片路径即可运行。

3. getAttribute 与 setAttribute 读写标签代码上的属性 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>getAttribute 与 setAttribute</title>
</head>
<body>
  <img
    id="imageBox"
    src="images/1.jpg"
    alt="小乐"
    loadPic="100.jpg"
    width="300"
  >

  <input type="checkbox" id="checkBook" checked>

  <button id="btn01">读取属性值</button>
  <button id="btn02">设置属性值</button>
  <button id="btn03">读取 checked 属性</button>

  <script>
    const imageBox = document.querySelector('#imageBox');
    const checkBook = document.querySelector('#checkBook');

    document.querySelector('#btn01').onclick = function () {
      console.log(imageBox.getAttribute('loadPic')); // "100.jpg"
      console.log(imageBox.getAttribute('src'));     // 原样字符串
      console.log(imageBox.getAttribute('width'));   // "300" 字符串
      console.log(imageBox.getAttribute('height'));  // null,代码上没写
    };

    document.querySelector('#btn02').onclick = function () {
      imageBox.setAttribute('src', 'images/2.jpg');
      imageBox.setAttribute('home-address', '北京');
    };

    document.querySelector('#btn03').onclick = function () {
      console.log(checkBook.getAttribute('checked')); // "" 或 null
    };
  </script>
</body>
</html>

关键行与易错点:

  • getAttribute('属性名') 读取的是标签代码上写过的属性,不区分内置属性和自定义属性。
  • 标签代码上没有写的属性,getAttribute 返回 null,例如 getAttribute('height')。
  • getAttribute('src') 返回原样字符串,不会像 imageBox.src 那样解析成绝对路径。
  • getAttribute('width') 返回字符串 "300",不是数字。
  • setAttribute('属性名', '值') 有两个参数;属性存在就修改,不存在就添加。
  • setAttribute 修改的是文档结构中的元素,不是直接改源代码。
  • 判断复选框是否选中应使用 checkBook.checked,不要用 getAttribute('checked')。
  • 自定义属性常用于在标签上藏数据,例如 loadPic="100.jpg";它本身没有标准含义,由开发者自己约定。

常见错误 ​

1. 集合 API 返回空集合,却被当成“没找到”来判空

  • 现象:const list = document.getElementsByTagName('li'); if (list) { list[0].style.color = 'red' } 在没有 li 时抛出 Cannot read properties of undefined。
  • 原因:getElementsByTagName / getElementsByClassName / querySelectorAll 找不到时返回空集合(不是 null),空集合本身是 truthy,if (list) 永远成立,但 list[0] 是 undefined。
  • 修法:判长度而不是判对象——if (list.length) {...};只有 getElementById 和 querySelector 才需要判 null。

2. 把 HTMLCollection / NodeList 当数组用

  • 现象:document.querySelectorAll('.item').map(el => el.textContent) 报 map is not a function;list.push(...) 报错。
  • 原因:两者都是伪数组,只有 length 和数字索引,原型上没有任何 Array 方法。
  • 修法:Array.from(list) 或 [...list] 转成真数组后再用 map/filter/forEach;遍历时用 for 循环或 forEach(NodeList 有 forEach,HTMLCollection 没有)。

3. 缓存了集合引用,循环中增删元素后结果“越遍历越多”

  • 现象:给所有 .item 加元素时,for 循环里追加了新的 .item,循环次数不断增加甚至死循环。
  • 原因:getElementsByClassName / children 返回的是动态集合,DOM 一变它立刻跟着变;而 querySelectorAll 返回的是静态快照。
  • 修法:涉及增删的遍历,统一用 querySelectorAll 拿快照;或者先 Array.from 固化再操作。

4. 用 getAttribute 判断布尔属性的状态

  • 现象:if (checkbox.getAttribute('checked')) 明明勾选了却进不去分支;反过来 if (el.getAttribute('disabled')) 在写了 disabled 时得到 "",被判为 false。
  • 原因:getAttribute 只读标签代码上写死的字符串,无值属性返回 "",它跟元素当前状态无关;用户交互改变的勾选状态不会反映到 attribute 上。
  • 修法:状态一律用点语法 el.checked / el.disabled / el.selected 读写布尔值;getAttribute 只用于读取代码里写死的初始值。

5. 想读“有没有这个属性”,却读了内置属性拿到默认值

  • 现象:if (img.width) 想判断标签上是否写了 width,结果没写也返回真实渲染宽度;if (img.src) 永远为真。
  • 原因:内置属性由标准预先挂到元素对象上,没写也有值(''、数字、解析后的绝对 URL)。
  • 修法:判断“代码里写没写”用 el.hasAttribute('width') 或 el.getAttribute('width') !== null;el.src 会被解析成绝对路径,要原样字符串就用 getAttribute('src')。

6. 脚本先于元素执行,拿到 null

  • 现象:<script> 写在 <head> 里,document.querySelector('#box') 返回 null,后续操作报错。
  • 原因:DOM 还没解析到该元素,查询时文档里确实不存在。
  • 修法:脚本放到 </body> 前;或包在 document.addEventListener('DOMContentLoaded', ...) 中;或用 defer。

7. 用关系属性时混入节点版 API

  • 现象:el.firstChild 拿到的是换行文本,el.previousSibling 拿到的也是空白文本,样式加不上。
  • 原因:childNodes / firstChild / previousSibling 面向所有节点,HTML 里的换行和缩进都是文本节点。
  • 修法:只用 Element 版——children / firstElementChild / lastElementChild / parentElement / previousElementSibling / nextElementSibling。

练习 ​

1. 高亮全部并标记序号 页面上有 6 个 .item。用一次查询拿到全部元素,为每个元素写入自定义属性 data-index(从 1 开始),并在元素文本后追加 #序号。要求:代码中不得出现 getElementsByClassName。

考点:querySelectorAll + 伪数组遍历 + setAttribute。

2. 点击卡片切换选中态 一个 .list 下有若干 .card,点击某张卡片时:该卡片加 active 类,其余卡片移除 active。只允许在点击事件里使用 parentElement 和 children 定位,不允许用 document.querySelectorAll 重新查询。

考点:元素关系定位 + classList 批量清理。

3. 图片查看器 一个 <img id="pic"> 和三个按钮。点击按钮 1 读取并打印图片的 src(要求打印出与标签代码里完全一致的字符串,而不是绝对路径);点击按钮 2 换成另一张图;点击按钮 3 读取标签上 alt 的原始值,若标签上压根没写 alt,打印 未设置。

考点:getAttribute 与内置属性 el.src 的差异、null 判定。

4. 手写 getSiblings 封装 function getSiblings(el),返回该元素的所有兄弟元素(不含自身),返回真数组。要求遍历时不能出现任何文本节点。

考点:parentElement.children + 过滤自身。

5. 找 bug

js
var btns = document.getElementsByClassName('btn');
if (btns) {
  for (var i = 0; i < btns.length; i++) {
    btns[i].onclick = function () {
      var box = document.getElementById('box');
      box.setAttribute('className', 'active');
      console.log(box.getAttribute('data-id'), box.checked);
    };
  }
}

指出至少 4 处问题并改正。

考点:集合判空、setAttribute 的属性名、getAttribute 与布尔状态的混用、属性名与自定义属性的取值方式。

原视频对照 ​

#视频时长
1获取元素-使用CSS选择器15:53
2获取元素-快捷方式03:43
3快速区分IE的一种方式05:56
4JS中会自动创建与ID同名的变量03:10
5文档结构-根据元素关系获取元素16:49
6属性操作-读写内置属性23:12
7属性操作-读写标签代码上的属性11:29

小结 ​

  1. 选型结论:日常一律用 querySelector / querySelectorAll;只在明确按 id 取单个元素时用 getElementById。getElementsByClassName、getElementsByName、document.all 属于“认识即可”,不进业务代码。
  2. 返回值只有两类,判断方式必须跟着变:单个元素 API 返回元素或 null,判 null;集合 API 返回集合(永不为 null),判 length。把这两类混着判空,是新手期最高频的线上报错来源。
  3. 集合的“死活”决定能不能缓存:getElementsBy*、children 是动态的,querySelectorAll 是静态快照。凡是循环中会改动 DOM 的场景,先转成真数组再动手。
  4. 关系属性只记 Element 系列六个(children、firstElementChild、lastElementChild、parentElement、previousElementSibling、nextElementSibling),节点版 API 在 HTML 里基本只会给你添乱。
  5. 属性操作是两套并行体系,不要混:点语法面向“元素对象的状态”,会做类型化处理(布尔、数字、绝对 URL);getAttribute/setAttribute 面向“标签上写的字符串”,原样进出、不区分内置与自定义、不做类型转换。
  6. 判断属性是否存在,用 hasAttribute / getAttribute(...) !== null;判断控件当前状态,用 el.checked 这类点语法。这两件事在语义上完全不同。
  7. 自定义属性(含 data-*)是 DOM 与业务数据之间的合法通道,用于携带“页面上看不见但要用”的信息;但它是约定而非契约,取值前要能容忍缺失。
  8. 两条别碰的坑:靠“ID 同名全局变量”省掉获取步骤(不可读、模块化下失效),用 document.all 做浏览器判断(IE11 已失效,且它被标准固化成 falsy 的兼容怪癖)。