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

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

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

学习

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

更多

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

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
JavaScript 核心/课时/综合练习与下一步
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综合练习与下一步

综合练习与下一步

小项目清单、React/Node 路线指引。

45 分钟

综合练习与下一步

这一课你将能做到

  1. 独立做一页「学习清单」小工具(增、删、切换完成状态)
  2. 对照清单自检:变量、函数、数组、DOM、基础错误处理都用到了
  3. 选准下一门课(TypeScript 或 React),并写下自己的薄弱点复习计划

为什么要学

前面每课都是零件:变量、函数、数组、对象、DOM、异步、模块、调试。
若不串成一个小作品,知识会停在「好像看过」。

这一课强制你手写。
能独立做完清单工具,再上 React/TS 时,你会知道框架到底替你省了哪一步。

先建立图像

数据(唯一真相)
  items = [{ id, title, done }, ...]
        ↓ 每次变更后
     render()
        ↓
      DOM 列表(只是投影)
操作改数据然后
添加push 新对象render
完成找到项,翻 donerender
删除filter/splicerender

别走「只改 DOM、不管数组」的路线——状态会很快不一致。

跟着做

步骤 1:搭页面骨架

新建 study-list.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>学习清单</title>
  <style>
    body { font-family: sans-serif; max-width: 480px; margin: 24px auto; padding: 0 12px; }
    .done { text-decoration: line-through; opacity: 0.6; }
    .err { color: #b91c1c; }
    li { margin: 8px 0; }
  </style>
</head>
<body>
  <h1>学习清单</h1>
  <input id="title" type="text" placeholder="例如:复习数组 map" />
  <button type="button" id="add">添加</button>
  <p id="hint" class="err" hidden></p>
  <ul id="list"></ul>
  <script src="./study-list.js"></script>
</body>
</html>

先用浏览器打开,确认标题与输入框在;控制台暂无报错(脚本文件下一步创建)。

步骤 2:建立数据与 render

新建同目录 study-list.js:

// 数据:唯一真相
const items = []; // { id: number, title: string, done: boolean }

const listEl = document.getElementById("list");
const hintEl = document.getElementById("hint");
const inputEl = document.getElementById("title");

function showHint(msg) {
  if (!msg) {
    hintEl.hidden = true;
    hintEl.textContent = "";
    return;
  }
  hintEl.hidden = false;
  hintEl.textContent = msg;
}

function render() {
  listEl.replaceChildren();
  for (const item of items) {
    const li = document.createElement("li");
    if (item.done) li.classList.add("done");

    const span = document.createElement("span");
    span.textContent = item.title;
    li.appendChild(span);

    const toggle = document.createElement("button");
    toggle.type = "button";
    toggle.textContent = item.done ? "标为未完成" : "完成";
    toggle.dataset.action = "toggle";
    toggle.dataset.id = String(item.id);

    const del = document.createElement("button");
    del.type = "button";
    del.textContent = "删除";
    del.dataset.action = "delete";
    del.dataset.id = String(item.id);

    li.append(" ", toggle, " ", del);
    listEl.appendChild(li);
  }
}

render(); // 初始为空列表也要跑通

刷新:应无红字;列表为空。

步骤 3:实现添加(含空输入校验)

document.getElementById("add").addEventListener("click", function () {
  const title = inputEl.value.trim();
  if (!title) {
    showHint("请输入内容后再添加");
    return;
  }
  showHint("");
  items.push({ id: Date.now(), title, done: false });
  inputEl.value = "";
  render();
});

// 回车也能添加(体验小细节)
inputEl.addEventListener("keydown", function (e) {
  if (e.key === "Enter") {
    document.getElementById("add").click();
  }
});

试:空添加应提示;输入文字添加后列表出现一项。

步骤 4:事件委托实现完成 / 删除

listEl.addEventListener("click", function (e) {
  const btn = e.target.closest("button[data-action]");
  if (!btn) return;

  const id = Number(btn.dataset.id);
  const action = btn.dataset.action;
  const idx = items.findIndex((it) => it.id === id);
  if (idx < 0) return;

  if (action === "toggle") {
    items[idx].done = !items[idx].done;
  } else if (action === "delete") {
    items.splice(idx, 1);
  }
  render();
});

试:点「完成」应出现删除线;再点恢复;点「删除」该项消失。

步骤 5:自检与加固

对照验收清单逐项打勾,并做两处加固:

// 1) 防止极端情况:id 不是数字
function findIndexById(id) {
  if (!Number.isFinite(id)) return -1;
  return items.findIndex((it) => it.id === id);
}

// 2) 可选:统计文案
function render() {
  listEl.replaceChildren();
  for (const item of items) {
    /* ...同上生成 li... */
  }
  document.title = `学习清单(${items.length})`;
}

打开控制台,全程操作一轮,确认无明显报错。

步骤 6:扩展选做(至少做一项)

任选其一,写进你的文件:

  1. 过滤:按钮「仅看未完成」,用 filter 派生再渲染
  2. 持久化:localStorage.setItem("items", JSON.stringify(items)),启动时 JSON.parse 读回(包 try/catch)
  3. 异步彩蛋:点「激励」按钮 await 一个 wait(300) 后显示随机一句鼓励

选做不是加戏炫技,而是把本轨道学过的点再压一遍。

讲清楚要点

  1. 因为 DOM 只是投影,所以所有变更先改 items,再 render。
  2. 因为列表项会增减,所以用事件委托,避免重复绑定。
  3. 因为空输入没有意义,所以添加前 trim 并提示。
  4. 因为完成状态是数据字段,所以用 done 布尔值驱动样式类,而不是只改一次 DOM 样式。
  5. 因为要可维护,所以拆 render / showHint 等小函数,避免一个巨型点击回调。
  6. 因为下一门课建立在这些习惯上,所以宁可实现朴素,也要状态清晰。

你可能会懵的地方

  • 渲染重复了 / 列表越点越多?
    每次 render 必须先清空(replaceChildren 或等价方式)。若只 append 不清空,就会叠一层又一层。

  • 事件绑多次?
    不要在 render 里给每个按钮 addEventListener 还重复调用;委托绑在 ul 上一次即可。

  • 想存到刷新后还在?
    用 localStorage + JSON(自行查 MDN);读写都要 try/catch,防止坏数据崩掉整页。

  • Date.now() 当 id 会不会冲突?
    练习足够;同一毫秒连点极端情况可用自增计数器。重点是「有稳定 id 方便查找」。

错法对照

错法问题正解
只改 DOM 不改数组刷新/重渲染后状态丢数据优先
用 innerHTML 拼用户标题XSS 风险textContent
全盘 var / 隐式全局难维护const/let
render 里重复绑监听一次点击触发多次委托或绑一次
空输入也 push一堆空白项trim + 提示

自测三问(总复习)

  1. 为什么清单数据放在数组里,而不是只写在 HTML 里?
  2. 点击后改的是 DOM 还是先改数据再渲染?你选了哪种?为何?
  3. 若进入 React,它帮你自动做了哪一件你现在手写的事(提示:UI 与状态同步)?

验收清单

  • 使用 const/let,无 var
  • 有函数(至少 render)
  • 修改文本用 textContent
  • 有空输入判断与提示
  • 控制台无明显报错
  • 完成 / 删除 / 添加三条路径都手测过

站内下一步

目标课程
类型更严/tracks/typescript-essentials
组件化 UI/tracks/react-essentials
补网页结构/tracks/html-css-essentials
岗位路线/learn?role=frontend

留一句

「JS 里我最容易懵的是 ,我准备这样练:。」

小结一句话

用一个小清单把变量、函数、数组、DOM 与基础校验串起来;能独立做完,再上框架才不虚。

延伸

  • 把作业文件夹留好,作为作品集第 0 号
  • MDN:JavaScript 指南 查漏补缺
  • 可选:为清单加上 localStorage 持久化,对照 JSON 课再走一遍 stringify/parse
加载本节练习…
← 上一课错误处理与调试