CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
JavaScript 核心/课时/DOM 与事件
RawBlame
课时目录12 节

JavaScript 核心

  1. 01JavaScript 是什么
  2. 02类型与变量
  3. 03运算符与类型转换
  4. 04条件与循环
  5. 05函数与作用域
  6. 06数组与常用方法
  7. 07对象与 JSON
  8. 08DOM 与事件
  9. 09异步 Promise 与 async/await
  10. 10ES Module 与 npm 概览
  11. 11错误处理与调试
  12. 12综合练习与下一步

DOM 与事件

选元素、改内容、监听点击与表单。

40 分钟

DOM 与事件

这一课你将能做到

  1. 用 querySelector / getElementById 找到页面元素并改文本
  2. 用 addEventListener 监听点击与表单提交,并正确使用 preventDefault
  3. 解释事件委托的直观好处(父元素统一听),能读懂 closest 示例

为什么要学

控制台算数很爽,但用户点的是页面。
DOM(Document Object Model)人话:浏览器把 HTML 变成一棵可操作的树;JS 通过它改页面。
事件人话:点击、输入、提交时浏览器发出通知,你的函数去接。

不会 DOM,就无法把变量/数组/函数接到真实界面上。
后面的 React 会帮你「声明式」更新界面,但底层仍是这些概念。

先建立图像

HTML 标签  →  浏览器建成 DOM 树  →  JS 找到节点改它
点击按钮  →  触发 click 事件  →  你绑定的函数运行
document
  └─ html
       ├─ head
       └─ body
            ├─ button#btn
            └─ p#out
API人话
getElementById按 id 精确找一个
querySelector按 CSS 选择器找第一个
querySelectorAll找一堆,再循环
addEventListener订阅某种事件
textContent改纯文本(优先)

跟着做

步骤 1:查找与修改

<p id="tip">原始文字</p>
<script>
  // CSS 选择器语法:#tip 表示 id=tip
  const tip = document.querySelector("#tip");
  tip.textContent = "被 JS 改过了";
</script>

优先用 textContent 改文本;少用 innerHTML 拼用户输入(有 XSS 风险:恶意脚本被插入页面)。

步骤 2:监听点击,维护计数状态

<button type="button" id="btn">+1</button>
<span id="n">0</span>
<script>
  let count = 0;
  const n = document.getElementById("n");
  document.getElementById("btn").addEventListener("click", function () {
    count = count + 1;          // 先改数据
    n.textContent = String(count); // 再更新界面
  });
</script>

你应看到每次点击数字加一。模式仍是:状态在变量里,DOM 是投影。

步骤 3:表单提交不要整页刷新

<form id="f">
  <label>留言 <input name="msg" required /></label>
  <button type="submit">发送</button>
</form>
<p id="out"></p>
<script>
  document.getElementById("f").addEventListener("submit", function (e) {
    e.preventDefault(); // 阻止默认「提交刷新」
    const fd = new FormData(e.target);
    const msg = String(fd.get("msg") || "").trim();
    const out = document.getElementById("out");
    if (!msg) {
      out.textContent = "留言不能为空";
      return;
    }
    out.textContent = "你说:" + msg;
  });
</script>

不写 preventDefault 时,浏览器可能整页刷新,你的 JS 状态会丢光。

步骤 4:事件委托(思想)

不要给 100 个按钮各绑一次,可以听父元素:

<ul id="list">
  <li><button type="button" data-id="1">任务一</button></li>
  <li><button type="button" data-id="2">任务二</button></li>
</ul>
<script>
  document.getElementById("list").addEventListener("click", function (e) {
    // 从实际点击目标往上找带 data-id 的 button
    const btn = e.target.closest("button[data-id]");
    if (!btn) return; // 点到空白处就忽略
    console.log("点了", btn.dataset.id);
  });
</script>

委托利用冒泡:事件从内向外传,父元素也能听到。

步骤 5:创建节点并挂到树上

const ul = document.getElementById("list");
const li = document.createElement("li");
const btn = document.createElement("button");
btn.type = "button";
btn.dataset.id = "3";
btn.textContent = "任务三";
li.appendChild(btn);
ul.appendChild(li);

你应在列表末尾看到新按钮;步骤 4 的委托监听无需重绑也能响应新按钮——这是委托的重要好处。

步骤 6:完整小页——待办一瞥

保存为 js-dom.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>DOM 与事件</title>
</head>
<body>
  <input id="title" type="text" placeholder="新任务" />
  <button type="button" id="add">添加</button>
  <ul id="list"></ul>
  <script>
    const items = []; // { id, title }

    function render() {
      const list = document.getElementById("list");
      list.replaceChildren(); // 清空再画(现代 API)
      for (const item of items) {
        const li = document.createElement("li");
        li.textContent = item.title + " ";
        const del = document.createElement("button");
        del.type = "button";
        del.textContent = "删除";
        del.dataset.id = String(item.id);
        li.appendChild(del);
        list.appendChild(li);
      }
    }

    document.getElementById("add").addEventListener("click", function () {
      const input = document.getElementById("title");
      const title = input.value.trim();
      if (!title) {
        alert("请输入内容"); // 入门演示;正式产品改用页面提示
        return;
      }
      items.push({ id: Date.now(), title });
      input.value = "";
      render();
    });

    document.getElementById("list").addEventListener("click", function (e) {
      const btn = e.target.closest("button[data-id]");
      if (!btn) return;
      const id = Number(btn.dataset.id);
      const idx = items.findIndex((it) => it.id === id);
      if (idx >= 0) {
        items.splice(idx, 1);
        render();
      }
    });
  </script>
</body>
</html>

添加与删除都应只改数组再 render。这是综合练习课的缩略版预热。

讲清楚要点

  1. 因为脚本可能早于元素出现,所以把脚本放 body 底部,或绑定前检查元素是否存在。
  2. 因为 innerHTML 会解析 HTML,所以展示用户输入优先 textContent,避免 XSS。
  3. 因为表单默认会提交刷新,所以前端处理时要 preventDefault。
  4. 因为事件会冒泡,所以可在父元素委托监听,动态子节点也省得重绑。
  5. 因为可访问性与语义重要,所以能用 button/a 就别用假 div 点击。
  6. 因为界面应由数据驱动,所以先改数组/对象,再统一渲染 DOM。

你可能会懵的地方

  • querySelector 只返回第一个
    多个用 querySelectorAll,再 for...of 循环。它返回的是「静态/类数组」节点列表,注意别当普通数组乱用(可 Array.from)。

  • 绑定了为什么没反应?
    查:id 是否写错、脚本是否报错、是否绑错元素、按钮是否在 form 里误提交导致刷新。

  • 事件冒泡与捕获
    默认多用冒泡阶段。委托就利用冒泡。捕获阶段入门可先不管。

  • replaceChildren 不支持?
    旧环境可 list.innerHTML = ""(仅在无不可信 HTML 时)或循环 removeChild。

错法对照

错法问题正解
innerHTML 塞用户输入XSS 风险textContent / 安全处理
在 form 里用 button 不写 type误当提交type="button" 或处理 submit
匿名函数无法移除监听难清理需要时保存函数引用再 removeEventListener
只改 DOM 不改数据状态不一致数据优先,再渲染
给每个动态按钮单独绑且重复绑泄漏/重复触发事件委托

自测三问

  1. DOM 用一句话怎么解释?
  2. 为什么表单 submit 常要 preventDefault?
  3. 事件委托主要省了什么?对新加的子按钮有何好处?

小结一句话

通过 DOM 找到并改节点,用事件监听用户操作;改文本优先 textContent,提交记得 preventDefault,列表交互优先「数据 → 渲染」并善用委托。

延伸

  • MDN:DOM 简介
  • MDN:事件简介
  • 下一课:异步——等待网络返回。
加载本节练习…
← 上一课对象与 JSON下一课 →异步 Promise 与 async/await