Skip to content

DOM 内容、属性和样式 ​

上一节已经能找到页面元素。现在页面预留了一张学员详情卡片:要把王五的姓名、班级、手机号和头像写入对应位置;如果他已停用,卡片样式、状态文字、提示和编辑按钮要一起变化。姓名等外部文字即使包含 HTML 标签,也应按普通文字显示。

本节课解决四个问题:

  1. 怎样读取和修改元素内容。
  2. 怎样操作图片、链接、表单和自定义属性。
  3. 怎样使用 style 和 classList 修改样式。
  4. 怎样安全地把接口文字显示到页面中。

内容与文本安全 ​

学员姓名、班级、手机号和接口提示都属于普通文字。先把这类数据安全地显示出来,再处理图片、按钮和状态样式。

textContent 文本写入 ​

显示姓名、班级、手机号等普通文字时,优先使用 textContent。

html
<h2 id="student-name">未填写</h2>
<p id="class-name">未分班</p>
js
const studentName = document.querySelector("#student-name");
const className = document.querySelector("#class-name");

studentName.textContent = "张三";
className.textContent = "Python 1 班";

执行后,两个元素原来的“未填写”“未分班”会被新文字替换。读取内容时也使用同一个属性:

js
console.log(studentName.textContent); // 张三

textContent 会把内容当成普通文字。即使字符串中包含 HTML 标签,也不会创建新元素。

js
studentName.textContent = "<strong>张三</strong>";

页面会直接显示:

text
<strong>张三</strong>

innerHTML 与文本安全 ​

innerHTML 会把字符串解析成 HTML。

html
<div id="message"></div>
js
const message = document.querySelector("#message");

message.innerHTML = "<strong>保存成功</strong>";

运行后,页面会生成一个新的 <strong> 元素。

属性作用常见场景
textContent读取或设置普通文字姓名、手机号、接口提示、用户输入
innerHTML读取或设置 HTML 结构项目自己编写的固定模板

选择原则很明确:数据只是文字时使用 textContent;只有字符串完全由项目自己编写,并且确实需要解析成标签时,才考虑 innerHTML。

不要把用户输入或接口返回的普通文字直接放进 innerHTML:

js
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 操作。

html
<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>
js
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:

js
console.log(mobileInput.value);
console.log(disabledInput.checked);
console.log(saveButton.disabled);

checked 和 disabled 是布尔值,不要和字符串比较:

js
if (disabledInput.checked) {
  console.log("当前复选框已选中");
}

HTML attribute ​

需要按 HTML 属性名称读取或设置时,可以使用下面的方法:

方法作用
getAttribute(name)读取属性字符串,属性不存在时返回 null
setAttribute(name, value)设置属性
removeAttribute(name)删除属性
hasAttribute(name)判断属性是否存在
toggleAttribute(name, force)添加或删除布尔属性
html
<a id="course-link" href="./course.html" title="课程详情">查看课程</a>
js
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 中使用相对路径:

html
<a id="help-link" href="./help.html">帮助</a>
js
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 中只要存在该属性,就表示启用。

html
<button id="submit-button" disabled>提交</button>

使用 property 修改最直接:

js
const submitButton = document.querySelector("#submit-button");

submitButton.disabled = false;
submitButton.disabled = true;

也可以使用 toggleAttribute():

js
submitButton.toggleAttribute("disabled", true);  // 添加 disabled
submitButton.toggleAttribute("disabled", false); // 删除 disabled

不要这样设置:

js
submitButton.setAttribute("disabled", "false");

属性仍然存在,所以按钮仍然是禁用状态。

data-* 与 dataset ​

页面需要保存学员编号、状态等少量辅助数据时,可以使用 data-*。

html
<article
  id="student-card"
  data-id="12"
  data-class-name="Python 1 班"
  data-disabled="false"
>
  学员信息
</article>

使用 dataset 读取:

js
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 读取的数据都是字符串。需要数字或布尔值时要主动转换:

js
const studentId = Number(studentCard.dataset.id);
const isDisabled = studentCard.dataset.disabled === "true";

console.log(studentId, isDisabled);

设置和删除:

js
studentCard.dataset.id = "20";
studentCard.dataset.disabled = "true";
delete studentCard.dataset.className;

dataset 适合保存与当前元素直接相关的简单值,不适合保存完整学员对象。复杂数据仍然放在 JavaScript 变量或接口中。

状态与样式 ​

内容和属性准备好后,还要根据学员状态改变卡片外观、提示和按钮。少量临时样式可以直接改 style;完整状态样式交给 CSS class。

行内样式 ​

元素的 style 属性表示行内样式。

html
<div id="notice">课程通知</div>
js
const notice = document.querySelector("#notice");

notice.style.color = "#ff5722";
notice.style.backgroundColor = "#fff3e8";
notice.style.fontSize = "18px";
notice.style.padding = "12px";

注意两点:

  1. CSS 中带连字符的属性,在 JavaScript 中通常改成驼峰形式,例如 background-color 变成 backgroundColor。
  2. 宽度、字号等值要带单位,例如 "200px",不能只写 200。

style 适合很少的、一次性的视觉调整。若同一个状态需要改边框、背景、文字颜色等多项样式,不要在 JavaScript 中逐条设置,后面使用 classList 统一控制。

也可以按 CSS 原名称设置:

js
notice.style.setProperty("background-color", "#fff3e8");
notice.style.removeProperty("background-color");

计算样式 ​

element.style 只能读取行内样式,不能直接读取 CSS 文件中的最终结果。

html
<style>
  #notice {
    width: 240px;
  }
</style>
js
console.log(notice.style.width); // 空字符串

const computedStyle = getComputedStyle(notice);
console.log(computedStyle.width); // 240px

getComputedStyle() 返回浏览器计算后的只读样式,常用于排错和需要读取实际尺寸的场景。

classList 状态样式 ​

当样式超过一两项时,不要在 JavaScript 中逐条设置。先在 CSS 中定义状态 class,再使用 classList 切换。

css
.student-card {
  border: 1px solid #dcdfe6;
  background: #fff;
}

.student-card.is-disabled {
  border-color: #c0c4cc;
  background: #f2f3f5;
  color: #909399;
}
js
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():

js
const isDisabled = true;
studentCard.classList.toggle("is-disabled", isDisabled);

这样不需要自己写两次 add() 和 remove()。

避免覆盖原有 class

className 会替换完整 class 字符串:

html
<article class="student-card shadow"></article>
js
studentCard.className = "is-disabled";

运行后,原来的 student-card 和 shadow 都会消失。项目中修改单个状态时优先使用 classList。

显示与隐藏 ​

简单显示和隐藏可以使用 hidden property:

html
<p id="disabled-message" hidden>这名学员已经停用</p>
js
const disabledMessage = document.querySelector("#disabled-message");

disabledMessage.hidden = false; // 显示
disabledMessage.hidden = true;  // 隐藏

项目需要过渡动画或多种显示状态时,使用 class:

css
.is-hidden {
  display: none;
}
js
disabledMessage.classList.toggle("is-hidden", true);

不要通过反复修改 style.display 猜测元素原来的显示模式。状态较多时统一使用 class 更容易维护。

综合案例:学员详情卡片 ​

案例需求 ​

页面先准备一张空的学员卡片,再根据 JavaScript 中的学员对象完成初始化:

  1. 显示姓名、班级和手机号。
  2. 修改头像地址和替代文字。
  3. 把学员编号保存到 data-id。
  4. 根据停用状态修改卡片、状态文字和提示。
  5. 学员停用时禁用编辑按钮。

学员数据 ​

本节从 p03 的学习记录转到管理页面,示例只保留卡片需要的字段,并增加头像与停用状态:

数据阶段字段用途
p03 数据处理course、score、status 用于筛选和统计,手机号放在 contact.mobile
当前本地卡片className、mobile、avatar、disabled 用于显示资料与控制按钮
后续 API 数据按接口契约读取 class_name、mobile、disabled 等字段

这是不同任务使用的数据模型。status 的多种学习状态不能直接等同于布尔值 disabled;接入 API 时应明确字段对应关系,不能只替换名称。

js
const student = {
  id: 3,
  name: "王五",
  className: "Python 2 班",
  mobile: "13800000003",
  avatar: "../../api_server/uploads/default.svg",
  disabled: true,
};

页面结构 ​

html
<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>

卡片初始化 ​

js
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 同时控制多个页面位置,避免它们出现不一致。

关键步骤

  1. 函数接收一个学员对象,集中完成卡片渲染。
  2. 姓名、班级和手机号都使用 textContent。
  3. 图片地址、替代文字和按钮禁用状态使用 property。
  4. 学员编号和状态通过 dataset 保存到卡片。
  5. classList.toggle(name, force) 保证 class 与数据状态一致。
  6. hidden 的值与 student.disabled 相反,所以使用 !student.disabled。

运行与验收 ​

直接打开学员详情卡片(源码文件:./.code/p04/c03-content-style-attribute/04-student-card.html)。默认应看到王五的姓名、班级、手机号和头像;状态为“已停用”,卡片为灰色,停用提示显示,编辑按钮不可点击。

可以按下面步骤验证:

  1. 把 student.disabled 改为 false,刷新页面。状态应变为“正常”,停用样式和提示消失,编辑按钮恢复可用。
  2. 把 student.name 改为 "<strong>测试</strong>",页面应直接显示这段文字,不会生成加粗标签。
  3. 在浏览器控制台查看 document.querySelector("#student-card").dataset,应能看到字符串形式的 id 和 disabled。

附件把页面结构、样式和初始化函数放在同一个 HTML 文件中,头像依赖 .code/api_server/uploads/default.svg。复制源码时同时保留这个相对路径;修改学员数据后刷新即可核对页面变化。

学员卡片完整源码
html
<!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
<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>

:::

小结 ​

本节课最核心的是掌握五件事:

  1. 普通文字使用 textContent,固定 HTML 结构才考虑 innerHTML。
  2. 表单状态、图片和链接优先使用 DOM property。
  3. 自定义属性可以使用 getAttribute()、setAttribute() 和 dataset。
  4. 少量行内样式使用 style,多项状态样式使用 classList。
  5. dataset 返回字符串,布尔属性应通过布尔 property 操作。