DOM 内容、属性和样式
上一节已经能找到页面元素。现在页面预留了一张学员详情卡片:要把王五的姓名、班级、手机号和头像写入对应位置;如果他已停用,卡片样式、状态文字、提示和编辑按钮要一起变化。姓名等外部文字即使包含 HTML 标签,也应按普通文字显示。
本节课解决四个问题:
- 怎样读取和修改元素内容。
- 怎样操作图片、链接、表单和自定义属性。
- 怎样使用
style和classList修改样式。 - 怎样安全地把接口文字显示到页面中。
内容与文本安全
学员姓名、班级、手机号和接口提示都属于普通文字。先把这类数据安全地显示出来,再处理图片、按钮和状态样式。
textContent 文本写入
显示姓名、班级、手机号等普通文字时,优先使用 textContent。
<h2 id="student-name">未填写</h2>
<p id="class-name">未分班</p>const studentName = document.querySelector("#student-name");
const className = document.querySelector("#class-name");
studentName.textContent = "张三";
className.textContent = "Python 1 班";执行后,两个元素原来的“未填写”“未分班”会被新文字替换。读取内容时也使用同一个属性:
console.log(studentName.textContent); // 张三textContent 会把内容当成普通文字。即使字符串中包含 HTML 标签,也不会创建新元素。
studentName.textContent = "<strong>张三</strong>";页面会直接显示:
<strong>张三</strong>innerHTML 与文本安全
innerHTML 会把字符串解析成 HTML。
<div id="message"></div>const message = document.querySelector("#message");
message.innerHTML = "<strong>保存成功</strong>";运行后,页面会生成一个新的 <strong> 元素。
| 属性 | 作用 | 常见场景 |
|---|---|---|
textContent | 读取或设置普通文字 | 姓名、手机号、接口提示、用户输入 |
innerHTML | 读取或设置 HTML 结构 | 项目自己编写的固定模板 |
选择原则很明确:数据只是文字时使用 textContent;只有字符串完全由项目自己编写,并且确实需要解析成标签时,才考虑 innerHTML。
不要把用户输入或接口返回的普通文字直接放进 innerHTML:
const apiName = '<img src="x" onerror="alert(1)">';
// 不安全:字符串会被浏览器当成 HTML 解析
studentName.innerHTML = apiName;
// 安全:只显示普通文字
studentName.textContent = apiName;后面渲染接口数据时,如果只是显示文字,一律优先使用 textContent。
元素属性
文字之外,图片地址、链接地址、输入框内容、按钮是否可点,都保存在元素属性或运行时状态中。先区分常用 property 和 HTML attribute,再根据场景选择。
DOM property
HTML 标签自带的 src、href、value、checked、disabled 等属性,可以直接通过 DOM property 操作。
<img id="student-avatar" src="./default.svg" alt="默认头像" />
<a id="detail-link" href="./detail.html">查看详情</a>
<input id="student-mobile" value="13800000001" />
<input id="student-disabled" type="checkbox" />
<button id="save-button">保存</button>const avatar = document.querySelector("#student-avatar");
const detailLink = document.querySelector("#detail-link");
const mobileInput = document.querySelector("#student-mobile");
const disabledInput = document.querySelector("#student-disabled");
const saveButton = document.querySelector("#save-button");
avatar.src = "./avatar-1.png";
avatar.alt = "张三的头像";
detailLink.href = "./detail.html?id=1";
mobileInput.value = "13900000001";
disabledInput.checked = true;
saveButton.disabled = true;执行后,头像和链接会更新,复选框被选中,保存按钮变为禁用。表单的实时状态要读取 property:
console.log(mobileInput.value);
console.log(disabledInput.checked);
console.log(saveButton.disabled);checked 和 disabled 是布尔值,不要和字符串比较:
if (disabledInput.checked) {
console.log("当前复选框已选中");
}HTML attribute
需要按 HTML 属性名称读取或设置时,可以使用下面的方法:
| 方法 | 作用 |
|---|---|
getAttribute(name) | 读取属性字符串,属性不存在时返回 null |
setAttribute(name, value) | 设置属性 |
removeAttribute(name) | 删除属性 |
hasAttribute(name) | 判断属性是否存在 |
toggleAttribute(name, force) | 添加或删除布尔属性 |
<a id="course-link" href="./course.html" title="课程详情">查看课程</a>const courseLink = document.querySelector("#course-link");
console.log(courseLink.getAttribute("href"));
courseLink.setAttribute("title", "查看前端基础课课程");
courseLink.setAttribute("target", "_blank");
courseLink.removeAttribute("target");
console.log(courseLink.hasAttribute("title")); // true这组方法按 HTML 属性名称工作。适合读取原始属性字符串、设置 title、target 等属性,或判断某个属性是否存在。
property 与 attribute 的区别
下面的链接在 HTML 中使用相对路径:
<a id="help-link" href="./help.html">帮助</a>const helpLink = document.querySelector("#help-link");
console.log(helpLink.getAttribute("href")); // ./help.html
console.log(helpLink.href); // 浏览器解析后的完整地址当前课程使用下面的选择原则:
- 表单实时状态使用 property,例如
value、checked、disabled。 - 图片和链接常用 property,例如
src、alt、href。 - 自定义属性和需要保留原始字符串的属性使用
getAttribute()、setAttribute()。
可以先这样理解:attribute 是 HTML 上写了什么,property 是浏览器当前正在使用什么。输入框被用户修改后,应读取 input.value,而不是只看最初写在 HTML 中的 value 属性。
布尔属性
disabled、checked、required、readonly 等属于布尔属性。HTML 中只要存在该属性,就表示启用。
<button id="submit-button" disabled>提交</button>使用 property 修改最直接:
const submitButton = document.querySelector("#submit-button");
submitButton.disabled = false;
submitButton.disabled = true;也可以使用 toggleAttribute():
submitButton.toggleAttribute("disabled", true); // 添加 disabled
submitButton.toggleAttribute("disabled", false); // 删除 disabled不要这样设置:
submitButton.setAttribute("disabled", "false");属性仍然存在,所以按钮仍然是禁用状态。
data-* 与 dataset
页面需要保存学员编号、状态等少量辅助数据时,可以使用 data-*。
<article
id="student-card"
data-id="12"
data-class-name="Python 1 班"
data-disabled="false"
>
学员信息
</article>使用 dataset 读取:
const studentCard = document.querySelector("#student-card");
console.log(studentCard.dataset.id); // "12"
console.log(studentCard.dataset.className); // "Python 1 班"
console.log(studentCard.dataset.disabled); // "false"data-class-name 在 JavaScript 中变成驼峰形式 dataset.className。
通过 dataset 读取的数据都是字符串。需要数字或布尔值时要主动转换:
const studentId = Number(studentCard.dataset.id);
const isDisabled = studentCard.dataset.disabled === "true";
console.log(studentId, isDisabled);设置和删除:
studentCard.dataset.id = "20";
studentCard.dataset.disabled = "true";
delete studentCard.dataset.className;dataset 适合保存与当前元素直接相关的简单值,不适合保存完整学员对象。复杂数据仍然放在 JavaScript 变量或接口中。
状态与样式
内容和属性准备好后,还要根据学员状态改变卡片外观、提示和按钮。少量临时样式可以直接改 style;完整状态样式交给 CSS class。
行内样式
元素的 style 属性表示行内样式。
<div id="notice">课程通知</div>const notice = document.querySelector("#notice");
notice.style.color = "#ff5722";
notice.style.backgroundColor = "#fff3e8";
notice.style.fontSize = "18px";
notice.style.padding = "12px";注意两点:
- CSS 中带连字符的属性,在 JavaScript 中通常改成驼峰形式,例如
background-color变成backgroundColor。 - 宽度、字号等值要带单位,例如
"200px",不能只写200。
style 适合很少的、一次性的视觉调整。若同一个状态需要改边框、背景、文字颜色等多项样式,不要在 JavaScript 中逐条设置,后面使用 classList 统一控制。
也可以按 CSS 原名称设置:
notice.style.setProperty("background-color", "#fff3e8");
notice.style.removeProperty("background-color");计算样式
element.style 只能读取行内样式,不能直接读取 CSS 文件中的最终结果。
<style>
#notice {
width: 240px;
}
</style>console.log(notice.style.width); // 空字符串
const computedStyle = getComputedStyle(notice);
console.log(computedStyle.width); // 240pxgetComputedStyle() 返回浏览器计算后的只读样式,常用于排错和需要读取实际尺寸的场景。
classList 状态样式
当样式超过一两项时,不要在 JavaScript 中逐条设置。先在 CSS 中定义状态 class,再使用 classList 切换。
.student-card {
border: 1px solid #dcdfe6;
background: #fff;
}
.student-card.is-disabled {
border-color: #c0c4cc;
background: #f2f3f5;
color: #909399;
}const studentCard = document.querySelector(".student-card");
studentCard.classList.add("is-disabled");
studentCard.classList.remove("is-disabled");
studentCard.classList.toggle("is-disabled");
console.log(studentCard.classList.contains("is-disabled"));常用方法:
| 方法 | 作用 |
|---|---|
add(name) | 添加 class |
remove(name) | 删除 class |
toggle(name) | 存在就删除,不存在就添加 |
toggle(name, force) | 根据布尔值决定添加或删除 |
contains(name) | 判断是否包含 class |
replace(oldName, newName) | 替换 class |
根据数据控制 class 时,推荐使用带第二个参数的 toggle():
const isDisabled = true;
studentCard.classList.toggle("is-disabled", isDisabled);这样不需要自己写两次 add() 和 remove()。
避免覆盖原有 class
className 会替换完整 class 字符串:
<article class="student-card shadow"></article>studentCard.className = "is-disabled";运行后,原来的 student-card 和 shadow 都会消失。项目中修改单个状态时优先使用 classList。
显示与隐藏
简单显示和隐藏可以使用 hidden property:
<p id="disabled-message" hidden>这名学员已经停用</p>const disabledMessage = document.querySelector("#disabled-message");
disabledMessage.hidden = false; // 显示
disabledMessage.hidden = true; // 隐藏项目需要过渡动画或多种显示状态时,使用 class:
.is-hidden {
display: none;
}disabledMessage.classList.toggle("is-hidden", true);不要通过反复修改 style.display 猜测元素原来的显示模式。状态较多时统一使用 class 更容易维护。
综合案例:学员详情卡片
案例需求
页面先准备一张空的学员卡片,再根据 JavaScript 中的学员对象完成初始化:
- 显示姓名、班级和手机号。
- 修改头像地址和替代文字。
- 把学员编号保存到
data-id。 - 根据停用状态修改卡片、状态文字和提示。
- 学员停用时禁用编辑按钮。
学员数据
本节从 p03 的学习记录转到管理页面,示例只保留卡片需要的字段,并增加头像与停用状态:
| 数据阶段 | 字段用途 |
|---|---|
| p03 数据处理 | course、score、status 用于筛选和统计,手机号放在 contact.mobile |
| 当前本地卡片 | className、mobile、avatar、disabled 用于显示资料与控制按钮 |
| 后续 API 数据 | 按接口契约读取 class_name、mobile、disabled 等字段 |
这是不同任务使用的数据模型。status 的多种学习状态不能直接等同于布尔值 disabled;接入 API 时应明确字段对应关系,不能只替换名称。
const student = {
id: 3,
name: "王五",
className: "Python 2 班",
mobile: "13800000003",
avatar: "../../api_server/uploads/default.svg",
disabled: true,
};页面结构
<article id="student-card" class="student-card">
<img
id="student-avatar"
class="student-avatar"
src="../../api_server/uploads/default.svg"
alt="默认头像"
/>
<div class="student-info">
<div class="student-heading">
<h2 id="student-name">未填写</h2>
<span id="student-status" class="status-badge">未知</span>
</div>
<p>班级:<span id="class-name">未分班</span></p>
<p>手机号:<span id="student-mobile">未填写</span></p>
<p id="disabled-message" class="disabled-message" hidden>
这名学员已经停用,暂时不能编辑。
</p>
<button id="edit-button" type="button">编辑学员</button>
</div>
</article>卡片初始化
function renderStudentCard(student) {
const card = document.querySelector("#student-card");
const avatar = document.querySelector("#student-avatar");
const name = document.querySelector("#student-name");
const className = document.querySelector("#class-name");
const mobile = document.querySelector("#student-mobile");
const status = document.querySelector("#student-status");
const disabledMessage = document.querySelector("#disabled-message");
const editButton = document.querySelector("#edit-button");
// 接口普通文字使用 textContent,避免被当成 HTML。
name.textContent = student.name;
className.textContent = student.className;
mobile.textContent = student.mobile;
// 图片和表单状态使用 DOM property。
avatar.src = student.avatar;
avatar.alt = `${student.name}的头像`;
editButton.disabled = student.disabled;
// data-* 只保存与卡片直接相关的简单数据。
card.dataset.id = String(student.id);
card.dataset.disabled = String(student.disabled);
// 根据同一个状态同时控制 class、文字和显示隐藏。
card.classList.toggle("is-disabled", student.disabled);
status.classList.toggle("is-disabled", student.disabled);
status.textContent = student.disabled ? "已停用" : "正常";
disabledMessage.hidden = !student.disabled;
}
renderStudentCard(student);调用 renderStudentCard(student) 后,函数会依次填写普通文字、更新图片和按钮状态、保存卡片编号,最后根据停用状态切换 class、状态文字和提示。一个 student.disabled 同时控制多个页面位置,避免它们出现不一致。
关键步骤
- 函数接收一个学员对象,集中完成卡片渲染。
- 姓名、班级和手机号都使用
textContent。 - 图片地址、替代文字和按钮禁用状态使用 property。
- 学员编号和状态通过
dataset保存到卡片。 classList.toggle(name, force)保证 class 与数据状态一致。hidden的值与student.disabled相反,所以使用!student.disabled。
运行与验收
直接打开学员详情卡片(源码文件:./.code/p04/c03-content-style-attribute/04-student-card.html)。默认应看到王五的姓名、班级、手机号和头像;状态为“已停用”,卡片为灰色,停用提示显示,编辑按钮不可点击。
可以按下面步骤验证:
- 把
student.disabled改为false,刷新页面。状态应变为“正常”,停用样式和提示消失,编辑按钮恢复可用。 - 把
student.name改为"<strong>测试</strong>",页面应直接显示这段文字,不会生成加粗标签。 - 在浏览器控制台查看
document.querySelector("#student-card").dataset,应能看到字符串形式的id和disabled。
附件把页面结构、样式和初始化函数放在同一个 HTML 文件中,头像依赖 .code/api_server/uploads/default.svg。复制源码时同时保留这个相对路径;修改学员数据后刷新即可核对页面变化。
学员卡片完整源码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DOM 内容、属性和样式</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #f5f7fa;
color: #303133;
font-family: Arial, "Microsoft YaHei", sans-serif;
}
.page-container {
width: min(760px, calc(100% - 32px));
margin: 60px auto;
}
.page-title {
margin: 0 0 24px;
font-size: 28px;
}
.student-card {
display: flex;
gap: 24px;
padding: 28px;
border: 2px solid transparent;
border-radius: 12px;
background: #fff;
box-shadow: 0 8px 24px rgb(0 0 0 / 7%);
}
.student-card.is-disabled {
border-color: #c0c4cc;
background: #f2f3f5;
color: #909399;
}
.student-avatar {
width: 128px;
height: 128px;
border-radius: 50%;
background: #ebeef5;
object-fit: cover;
}
.student-info {
flex: 1;
}
.student-heading {
display: flex;
align-items: center;
gap: 12px;
}
.student-heading h2 {
margin: 0;
font-size: 24px;
}
.status-badge {
padding: 4px 10px;
border-radius: 12px;
background: #e8f8f5;
color: #16a091;
font-size: 13px;
}
.status-badge.is-disabled {
background: #dcdfe6;
color: #606266;
}
.student-info p {
margin: 14px 0;
}
.disabled-message {
padding: 10px 12px;
border-radius: 6px;
background: #fdf6ec;
color: #e6a23c;
}
#edit-button {
padding: 9px 18px;
border: 0;
border-radius: 5px;
background: #16baaa;
color: #fff;
cursor: pointer;
}
#edit-button:disabled {
background: #c0c4cc;
cursor: not-allowed;
}
</style>
</head>
<body>
<main class="page-container">
<h1 class="page-title">学员详情</h1>
<article id="student-card" class="student-card">
<img
id="student-avatar"
class="student-avatar"
src="../../api_server/uploads/default.svg"
alt="默认头像"
/>
<div class="student-info">
<div class="student-heading">
<h2 id="student-name">未填写</h2>
<span id="student-status" class="status-badge">未知</span>
</div>
<p>班级:<span id="class-name">未分班</span></p>
<p>手机号:<span id="student-mobile">未填写</span></p>
<p id="disabled-message" class="disabled-message" hidden>
这名学员已经停用,暂时不能编辑。
</p>
<button id="edit-button" type="button">编辑学员</button>
</div>
</article>
</main>
<script>
const student = {
id: 3,
name: "王五",
className: "Python 2 班",
mobile: "13800000003",
avatar: "../../api_server/uploads/default.svg",
disabled: true,
};
function renderStudentCard(student) {
const card = document.querySelector("#student-card");
const avatar = document.querySelector("#student-avatar");
const name = document.querySelector("#student-name");
const className = document.querySelector("#class-name");
const mobile = document.querySelector("#student-mobile");
const status = document.querySelector("#student-status");
const disabledMessage = document.querySelector("#disabled-message");
const editButton = document.querySelector("#edit-button");
// 接口普通文字使用 textContent,避免被当成 HTML。
name.textContent = student.name;
className.textContent = student.className;
mobile.textContent = student.mobile;
// 图片和表单状态使用 DOM property。
avatar.src = student.avatar;
avatar.alt = `${student.name}的头像`;
editButton.disabled = student.disabled;
// data-* 只保存与卡片直接相关的简单数据。
card.dataset.id = String(student.id);
card.dataset.disabled = String(student.disabled);
// 根据同一个状态同时控制 class、文字和显示隐藏。
card.classList.toggle("is-disabled", student.disabled);
status.classList.toggle("is-disabled", student.disabled);
status.textContent = student.disabled ? "已停用" : "正常";
disabledMessage.hidden = !student.disabled;
}
renderStudentCard(student);
</script>
</body>
</html><svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<rect width="128" height="128" rx="16" fill="#f2f3f5"/>
<circle cx="64" cy="46" r="23" fill="#c9cdd4"/>
<path d="M24 112c4-25 19-38 40-38s36 13 40 38" fill="#c9cdd4"/>
</svg>:::
小结
本节课最核心的是掌握五件事:
- 普通文字使用
textContent,固定 HTML 结构才考虑innerHTML。 - 表单状态、图片和链接优先使用 DOM property。
- 自定义属性可以使用
getAttribute()、setAttribute()和dataset。 - 少量行内样式使用
style,多项状态样式使用classList。 dataset返回字符串,布尔属性应通过布尔 property 操作。