跳转至

从零造一个网站 — 待办清单应用的完整流程

这一章讲一件事:把前 12 章学的全部东西,装进一个真的能跑的应用里。 项目是一个待办清单:加任务、标完成、删任务、刷新不丢。 这是全书最重要的一章——不是因为知识新,而是因为它第一次把「规划 → 实现 → 测试 → 调试」的完整闭环走给你看。 读完你会知道:一个网页应用不是「写出来的」,是「一层一层长出来的」。

1. 先看现象:四个功能,一座桥

最终成品的全部功能,书里在开始写代码之前就列死了1:

  ① 加任务      ② 标完成(再点可取消)     ③ 删任务      ④ 刷新不丢

  图说:前三个是「交互」,第四个是「持久化」——前三章的 DOM 本事管交互,
       本章新登场的 localStorage 管持久化;两者在「增删改查闭环」这座桥上会师。

这四个字「before writing any code」是本章的第一个方法论:先定功能,再动手。 需求不清就写代码,等于不打地基先盖三楼——书用一整节演示了「四个 bullet 的需求清单」长什么样, 这是真实项目里最被低估的一步。

2. 顶层全景:全流程一张图

  规划(功能清单 + 界面草图)
      ↓
  建工程:三个文件 index.html / styles.css / script.js
      ↓
  HTML:结构(header 输入区 + ul 空列表)
      ↓
  CSS:盒模型开局(*{box-sizing:border-box})→ flexbox 排布 → 状态样式(.checked)
      ↓
  JavaScript:加载 → 新增 → 切换 → 删除 → 保存,五动作闭环 + 事件委托统一入口
      ↓
  测试:功能 / 界面 / 响应式 / 跨浏览器 四类
      ↓
  调试:Console 报错 → Sources 定位行 → Elements 发现拼写错误 → 修复

  图说:七步就是本章七节;前两步是「想」,中间三步是「做」,最后两步是「验」。

3. 规划:草图是程序员的第一张合同

书里在写任何代码之前,先画了一张线框图(wireframe)——只用方框和文字标注的界面草图2: 顶部一块标题区(应用名 + 输入框 + Add 按钮)、下面任务列表、每条任务右侧 Done 和 Remove 两个按钮。

草图解决两个具体问题:

  • 结构先行:草图上的每一块,将来对应 HTML 的一个元素块(标题区 → 一个 div,列表 → ul,按钮 → button);
  • 验收有据:第 6 节的「界面测试」就是拿这张草图逐条对照——草图是「界面应该长什么样」的合同,测试是验合同。

这一步的成本是一支烟的工夫,收益是后面每一节都不跑偏。这是全章最便宜也最容易被跳过的一步。

4. 三文件工程与 HTML 骨架

项目文件夹(第 02 章的规矩)里建三个文件:index.html、styles.css、script.js3—— 结构、样子、行为三权分立,第 07 章的纪律第一次完整落地。

HTML 侧的关键行,每一行都有出处:

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>To-Do List</title>
    <link rel="stylesheet" href="styles.css">    <!-- 引样式:第 07 章 -->
</head>
<body>
    <div id="myDIV" class="header">
        <h2>My To-Do List</h2>
        <input type="text" id="myInput" placeholder="Enter a task...">
        <button onclick="newElement()" class="addBtn">Add</button>
    </div>
    <ul id="myUL"></ul>                          <!-- 空列表:任务全由 JS 动态生成 -->
    <script src="script.js"></script>            <!-- 脚本在 body 末尾:第 11 章的规矩 -->
</body>

两个细节值得停:

onclick="newElement()" 是「行内事件」——直接把调用写在 HTML 里,点按钮就执行那个函数。 它和 addEventListener 做同一件事,但书在本章两种都用了(行内 onclick 管 Add 按钮,addEventListener 管列表点击)—— 行内写法胜在直观,addEventListener 胜在能动态挂载(第 12 章的委托必须用它)。 真实项目一律后者;这里是教学混合。

<ul id="myUL"></ul> 是空的。 页面上看到的每条任务,都是 JavaScript 后来挂上去的—— 「内容全部由程序生成」是动态应用和静态页面的分水岭,第 12 章的 createElement 在这里量产。

5. CSS:开局两行,骨架一套

样式部分,书里逐选择器讲了一遍——大部分是第 07、08、10 章的复盘,真正的增量只有两行, 但它们是该应用的地基:

* {
    box-sizing: border-box;    /* 第 07/10 章的生命线:width 即总宽 */
}

body {
    display: flex;
    flex-direction: column;    /* 竖向排:标题区 → 列表 */
    align-items: center;       /* 全部内容水平居中 */
    min-width: 250px;          /* 再窄就不像话了:响应式保底 */
}

box-sizing: border-box 放在 * 上开局,是第 10 章那行解药的实战部署—— 输入框 width: 70% + Add 按钮 width: 25% + padding,没有它俩必溢出容器4。 min-width: 250px 是本章的响应式哲学:这个应用没有写一条媒体查询(第 09 章的正式武器), 靠的是 flexbox 的天然弹性 + 一个最小宽度保底——简单布局用「弹性 + 保底」,复杂布局才上媒体查询, 这是比背断点表更实用的判断。

任务项的样式里藏着一个「状态即类」的经典模式:

ul li { display: flex; justify-content: space-between; … }  /* 文字左、按钮右 */
ul li.checked { background: #2ecc71; }                       /* 完成 = 换肤 */
ul li.checked .task-text { text-decoration: line-through; color: white; }

.checked 类就是「已完成」这个状态的开关——JavaScript 切换类,CSS 负责样子, 行为与样式再次解耦。这正是第 07 章「改 classList 而不是改 style」的完整实战。

书里随后把每个选择器逐条过了一遍,我们把它收成一张「骨架表」——每一行的数值都是书里的原值, 值得对一遍,因为你以后写自己的应用时,骨架数值的选法与此同理17:

选择器 关键属性(书里的值) 在干什么
body display: flex; flex-direction: column; align-items: center; 竖排 + 水平居中整个应用
body min-width: 250px; 屏幕再窄也保住基本可用宽度
.header background: #f44336; max-width: 400px; border-radius: 8px; 红色标题块,圆角,限宽
input width: 70%; outline: none; 输入框占七成宽;去掉点击时的默认蓝框
.addBtn width: 25%; transition: 0.3s; 按钮占两成半;颜色变化有 0.3 秒过渡
ul width: 100%; max-width: 400px; 列表与标题块同宽(400px 上限)
ul li display: flex; justify-content: space-between; 任务文字靠左、按钮靠右
ul li box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 每条任务底下一点淡影,制造「卡片」感
ul li.checked background: #2ecc71; + text-decoration: line-through; 完成 = 绿底 + 划线
.done / .remove #007bff / #f44336 蓝 = 正向动作,红 = 删除,颜色即语义

两个贯穿性的手法值得点名:max-width 反复出现(内容不无限变宽,居中才有「应用感);rgba(0, 0, 0, 0.1)` 是半透明黑——比实色灰更柔和的阴影,这是第 07 章没教的 rgba 写法的第一次实战。

6. JavaScript:五动作闭环 + 一个统一入口

全书的功能逻辑都装在 script.js 里,书里拆成了六个函数 + 一个监听器。 我们按数据怎么流动来讲,比按函数顺序讲更看得懂:

持久化层:localStorage 与 JSON

localStorage 是浏览器内置的一个「访客本地小仓库」:每个网站可以存字符串键值对, 关浏览器、刷新都不丢——它是「没有后端」的应用唯一的数据库。

  存(保存任务):saveTasks(tasks)
      localStorage.setItem("tasks", JSON.stringify(tasks));
      → tasks 数组变成 JSON 字符串(如 '[{"text":"买菜","completed":false}]')存进去
      → localStorage 只存字符串,所以数组必须先"串行化"                    —— JSON.stringify

  取(读取任务):getStoredTasks()
      return JSON.parse(localStorage.getItem("tasks")) || [];
      → getItem 拿到字符串;JSON.parse 还原成数组
      → 什么都没存过时 getItem 返回 null,|| [] 兜底成空数组             —— JSON.parse

  图说:stringify 存进去、parse 取出来,一对相反的操作围着 localStorage 转;
       JSON 就是那个"数组和字符串互相变身"的格式。

这一对操作书里讲得清楚:localStorage 只认字符串,所以数组进仓库前必须 JSON.stringify, 出仓库后必须 JSON.parse 还原;取不到时用 || [] 给空数组兜底,防止对 null 做遍历56。

应用层:加载与增删改

加载 loadTasks():页面一就绪就运行(见下「统一入口」),取出存过的任务, 对每个任务调 createTaskElement(task.text, task.completed) 造一个 <li> 挂进列表7。

新增 newElement():取输入框的值 → .trim() 去掉首尾空格 → 空则 alert 拒绝 → 和已有任务比对防重复(tasks.some(task => task.text === taskText))→ createTaskElement(text, false) 造元素挂上 → saveTasks 存档 → 清空输入框8。 三步校验(空、重复、正常)是表单数据的卫生课——第 06 章 HTML5 校验管「格式」,这里的 JS 校验管「业务」。

切换完成 toggleTask(li):读任务文字 → classList.toggle("checked") 翻状态类 → 按钮文字 Done/Undo 对调 → 用 .map() 在存档里把这条的 completed 改掉再存9。 「翻类 + 改存档」是两份同步的状态:页面上是类,仓库里是 JSON,函数负责两边一致。

删除 deleteTask(li):li.remove() 从页面摘掉 → 用 .filter() 在存档里把这条滤掉再存10。 注意两个数组方法的分工与第 11 章呼应:map 是「每条过一遍、改符合条件的」, filter 是「留下不符合条件的」——删,就是滤。

造元素 createTaskElement(text, completed):用模板字符串拼好一条任务的 HTML (<span class="task-text"> + 两个按钮),completed 为真就先挂上 checked 类,返回造好的 <li>11。 书里这个函数是「工厂」——所有任务(不管来自存档还是新输入)都从这同一个模子里出来,样式和结构不会跑偏。

统一入口:两个监听器

document.addEventListener("DOMContentLoaded", loadTasks);
//   「HTML 全部解析完」这个事件一发生,就执行 loadTasks
//   ——保证 loadTasks 跑的时候,页面上的元素都已经存在(第 11 章时序问题的标准答案)

taskList.addEventListener("click", function (e) {
    if (e.target.classList.contains("done")) {
        toggleTask(e.target.parentElement.parentElement);
    } else if (e.target.classList.contains("remove")) {
        deleteTask(e.target.parentElement.parentElement);
    }
});
//   事件委托(第 12 章):一个监听器管所有任务的按钮,动态新增的任务自动被覆盖

DOMContentLoaded 书里给了准确定义:HTML 完全解析时触发,确保任何 JavaScript 运行之前所有元素都可用12—— 这正是 defer 的效果,只是换成了事件写法。 委托那段代码是第 12 章理论的完整落地:e.target.classList.contains("done") 认出按的是哪个按钮, e.target.parentElement.parentElement 沿树向上两格(按钮 → 按钮容器 → <li>)拿到整条任务13。

主线例子:加一条「Buy groceries」的完整旅程

  用户在输入框打:  "Buy groceries"  → 点 Add
      │ newElement() 拿到 input.value.trim() = "Buy groceries"(非空,通过)
      │ getStoredTasks() 取出存档,比如已有 [{"text":"Email Anna","completed":false}]
      │ .some() 比对:没有重名 → 放行
      │ createTaskElement("Buy groceries", false) 造出 <li>(绿条皮肤未挂)
      │ taskList.appendChild → 页面列表多一条
      │ saveTasks([...旧数组, {text:"Buy groceries", completed:false}])
      │   → localStorage 里的 "tasks" 变成两条的 JSON
      │ input.value = "" → 输入框清空
  用户点这条的 Done:
      │ 点击冒泡到 taskList → e.target 是 Done 按钮 → contains("done") 命中
      │ toggleTask(<li>):classList 挂上 checked → 绿底 + 划线;按钮字变 Undo
      │ 存档里这条 completed: true → saveTasks 存回
  用户刷新页面:
      │ DOMContentLoaded → loadTasks → getStoredTasks 读到两条
      │ 每条 createTaskElement(text, completed) → "Email Anna" 普通,"Buy groceries" 带 checked
      │ 两条挂回列表 → 状态完整还原

  图说:一次新增 = 页面 + 仓库两边同步写入;一次刷新 = 从仓库整体重建页面。
       (例子里的任务文字沿用书中调试段的 "Buy groceries"。)

7. 测试:四类清单

功能做完,书里给了一张四类测试清单——这几乎是真实项目测试计划的入门版14:

类别 验什么 书里列的要点
功能测试 每个功能按一遍 加任务出现且输入框清空;空/重复被拒;Done 变绿变 Undo;Remove 消失;刷新后状态还原
界面测试 对照草图逐块看 标题居中、输入框与按钮对齐、任务有间距、按钮都在、颜色可读、拼写
响应式测试 各尺寸可用 真机 + 拖窗口;小屏不溢出、不变形;min-width 生效
跨浏览器测试 四家引擎 Chrome、Firefox、Edge、Safari——第 01 章引擎表的现实意义

两个细节是经验之谈:「刷新后状态还原」单列一条(持久化最容易漏测); 界面测试里单列「拼写」——不是凑数,错别字是界面测试最稳定的发现项。 响应式测试里那句「min-width (250px) is enforced and prevents unusable narrow views」, 把第 5 节的保底策略闭环了15。

书里那张功能测试清单,展开后其实是四条「动作 → 预期」的对子,照抄就是一份可直接执行的冒烟测试脚本18:

动作 预期看到
输入文字点 Add 列表多出一条;输入框自动清空
什么都不输点 Add 弹提示「You must write something!」,列表不变
输一个已有任务点 Add 无反应(防重复),列表不变
点某条的 Done 任务变绿底 + 划线;按钮文字变成 Undo
点 Undo 恢复普通外观,按钮变回 Done
点某条的 Remove 该条从列表消失
连加几条后刷新页面 全部任务原样回来,完成的还是绿的

写测试用例的格式就在这里:每个用例 = 一个动作 + 一个可观察的预期。 「预期」必须是眼睛能看到或断言能判的(变绿、消失、弹窗),不能是「应该没问题」。 这套格式放到任何项目里都成立——这是本章最容易被低估的带走项。

8. 调试:顺着线索往回追

本章的最后一块,是全书最精彩的一段教学:书里故意埋了一个 bug,然后带你抓它。

现场还原:应用做好后,输入「Buy groceries」点 Add——什么都没发生16。 调试的定义,书里给得很正:调试不是改代码,是「理解代码在运行时实际在做什么,并拿它对比它应该做什么」19。 抓 bug 的三步,全在开发者工具(第 03 章那台 X 光机)里完成:

  第 1 步:看 Console(控制台)
      红色报错:Cannot read properties of null (reading 'value')
      ——翻译:有代码在读 null 的 value 属性(第 12 章预告过的第一大报错,现场兑现)
  第 2 步:顺着报错里的链接点进 Sources(源码)面板
      跳到 script.js 第 19 行:input.value.trim() ——就是这里读的
      问题:为什么 input 是 null?document.getElementById("myInput") 没找到东西
  第 3 步:去 Elements(元素)面板核对 HTML
      找到了:HTML 里写的是 id="myInpoot" ——多了一个 o!脚本找 "myInput",页面给 "myInpoot"
      一字之差,getElementById 老老实实返回 null

  修复:把 HTML 里的 id 改回 myInput,刷新,应用活了。

  图说:报错信息 → 出错行 → 元素核对,三步都是从浏览器给的线索往回走;
       调试不是猜,是顺着线索走。

这段教学的价值在于它演示的是方法,不是这个 bug:换一个 bug,Console/Sources/Elements 的动线不变。 书里那句比喻值得带走——把调试当侦探工作:浏览器给你线索,你的工作是跟着线索走、修根因,不是修症状20。 (改 script.js 里 getElementById("myInpoot") 也能「修好」,但那是修症状——HTML 里两处引用、CSS 选择器 全都对不上,迟早再炸。这是 ④ 类经验,书里演示的是改 HTML 的正路。)

9. 作者的判断与证据

书内作为事实给出的: 全流程的每一步都有书内出处(见原文地图);localStorage 只存字符串 及 JSON.stringify/parse 的配对使用56、DOMContentLoaded 的时序语义12、 事件委托在此处的必要性(新任务持续加入,逐按钮挂监听管不过来)21。

作者的方法论,值得当默认值: 「先定功能再写码」「草图即合同」「四类测试清单」「调试修根因」—— 这些是这一行的通行工程纪律,书里不是当知识教,是当做法示范;照做即可。

需要意识到的时代印记: 这个应用没有任何构建工具、没有任何框架、数据存在访客浏览器里—— 它是「2026 年还能成立的最简单应用形态」。真实产品会有后端、会有多端同步、会有登录; 但作为「第一个完整闭环」,它的简单恰恰是对的复杂度。

10. 边界与局限

  • localStorage 的边界没展开:容量约 5MB、只存字符串、清浏览器数据就全丢、不同步到其他设备——这些本书一句没提。把它当「真数据库」用会翻车;它是「没有后端的应用」的过渡方案。
  • 编辑任务、任务排序、分类,这些常见需求没做——章末习题给了一个更大的「笔记应用」当练习(增、改、删、持久化、响应式全要),正好补上这个台阶。
  • 没有部署环节:应用活在 Live Server 里;怎么放到公网上(托管、域名)是本书明确不覆盖的下一步。
  • onclick 行内写法与 addEventListener 混用(见 §4),真实项目应统一为后者。

11. 可带走的

全章主线例子,一行写完: 打「Buy groceries」→ 校验(非空、不重复)→ 造 <li> 挂上 → 存档(JSON.stringify)→ 点 Done 翻类 + 改存档 → 刷新后 DOMContentLoaded 触发 loadTasks 从存档(JSON.parse)整体重建。

  1. 先定功能清单,再写一行代码;草图是界面验收的合同12;
  2. 三文件工程(index.html / styles.css / script.js)是前端的「三权分立」;
  3. *{box-sizing:border-box} 开局;简单响应式 = flexbox 弹性 + min-width 保底,复杂才上媒体查询4;
  4. localStorage 只存字符串:存 stringify、取 parse、null 兜底 || []56;
  5. 五动作闭环(加载/新增/切换/删除/保存)+ 一个造元素的「工厂函数」,结构不散;
  6. DOMContentLoaded 保证脚本运行时 DOM 已就绪12;事件委托管动态元素13;
  7. 测试四类:功能、界面、响应式、跨浏览器;「刷新还原」和「拼写」都要单列;
  8. 调试动线:Console 看报错 → Sources 跳行 → Elements 核对 → 修根因19;
  9. getElementById 返回 null 的实体,就是 HTML 里一个字母打错(myInpoot)16——大小写和拼写,是 DOM 报错两大源头。

12. 原文地图

主题 原书章 原文位置
需求先行:四个核心功能 Building a Responsive Website from Scratch text/19-fm-introduction.txt:27(搜「basic To-Do List app」)
界面草图(wireframe) Building a Responsive Website from Scratch text/19-fm-introduction.txt:55(搜「Wireframe」)
三文件工程 Building a Responsive Website from Scratch text/19-fm-introduction.txt:65(搜「Open Folder」) · text/19-fm-introduction.txt:69(搜「index.html」)
开局 box-sizing 与居中三件套 Building a Responsive Website from Scratch text/19-fm-introduction.txt:149(搜「box-sizing: border-box」) · text/19-fm-introduction.txt:147(搜「min-width: 250px」)
全应用样式骨架表(各选择器数值) Building a Responsive Website from Scratch text/19-fm-introduction.txt:165(搜「#f44336」) · text/19-fm-introduction.txt:271(搜「rgba(0, 0, 0, 0.1)」) · text/19-fm-introduction.txt:275(搜「justify-content: space-between」) · text/19-fm-introduction.txt:345(搜「#007bff」)
功能测试清单(动作→预期) Building a Responsive Website from Scratch text/19-fm-introduction.txt:581(搜「Add task:」) · text/19-fm-introduction.txt:603(搜「Local storage persistence」)
checked 状态即类 Building a Responsive Website from Scratch text/19-fm-introduction.txt:295(搜「ul li.checked」)
DOMContentLoaded Building a Responsive Website from Scratch text/19-fm-introduction.txt:377(搜「DOMContentLoaded」)
getStoredTasks 与 JSON.parse 兜底 Building a Responsive Website from Scratch text/19-fm-introduction.txt:397(搜「JSON.parse」)
newElement:trim、防空、防重 Building a Responsive Website from Scratch text/19-fm-introduction.txt:449(搜「trim()」) · text/19-fm-introduction.txt:463(搜「Prevent duplicates」)
createTaskElement 工厂 Building a Responsive Website from Scratch text/19-fm-introduction.txt:415(搜「createTaskElement」)
toggleTask:翻类与 map 改存档 Building a Responsive Website from Scratch text/19-fm-introduction.txt:483(搜「classList.toggle」) · text/19-fm-introduction.txt:493(搜「tasks.map」)
deleteTask:remove 与 filter Building a Responsive Website from Scratch text/19-fm-introduction.txt:525(搜「filter(task =>」)
saveTasks:stringify 存库 Building a Responsive Website from Scratch text/19-fm-introduction.txt:543(搜「JSON.stringify」)
事件委托入口 Building a Responsive Website from Scratch text/19-fm-introduction.txt:551(搜「addEventListener("click"」)
四类测试 Building a Responsive Website from Scratch text/19-fm-introduction.txt:577(搜「Functional testing」) · text/19-fm-introduction.txt:609(搜「User interface testing」) · text/19-fm-introduction.txt:625(搜「Responsive design testing」) · text/19-fm-introduction.txt:639(搜「Cross-browser compatibility」)
调试的定义 Building a Responsive Website from Scratch text/19-fm-introduction.txt:653(搜「Debugging is the process」)
人为埋的拼写 bug Building a Responsive Website from Scratch text/19-fm-introduction.txt:667(搜「myInpoot」) · text/19-fm-introduction.txt:685(搜「Typo!」)
章末要点:委托与 JSON Building a Responsive Website from Scratch text/19-fm-introduction.txt:713(搜「event delegation」) · text/19-fm-introduction.txt:717(搜「stringify」)

  1. 出处:「Building a Responsive Website from Scratch」第 27 段(text/19-fm-introduction.txt:27,搜「basic To-Do List app」)。原文:「Before writing any code, we need to define the features of the website.」四个功能:add tasks、mark completed、remove tasks、save in local storage。 ↩↩

  2. 出处:「Building a Responsive Website from Scratch」第 39–55 段(text/19-fm-introduction.txt:55,搜「Wireframe design」)。草图列出五件:Header、Input field、Add button、Task list、Action buttons(Done 划线、Remove 删除)。 ↩↩

  3. 出处:「Building a Responsive Website from Scratch」第 63–77 段(text/19-fm-introduction.txt:67,搜「three files」;:69,搜「index.html」;:71,搜「styles.css」;:73,搜「script.js」)。 ↩

  4. 出处:「Building a Responsive Website from Scratch」第 149–157 段(text/19-fm-introduction.txt:149,搜「box-sizing: border-box」;:157,搜「do not increase its overall width」)。 ↩↩

  5. 出处:「Building a Responsive Website from Scratch」第 395–401 段(text/19-fm-introduction.txt:397,搜「JSON.parse」)。原文:「This fetches and parses the saved tasks from localStorage... If no tasks exist, an empty array is returned to prevent errors.」 ↩↩↩

  6. 出处:「Building a Responsive Website from Scratch」第 539–545 段(text/19-fm-introduction.txt:539,搜「JSON.stringify」;:543,搜「localStorage.setItem」)。原文:「localStorage can only store data as strings.」 ↩↩↩

  7. 出处:「Building a Responsive Website from Scratch」第 389–409 段(text/19-fm-introduction.txt:405,搜「forEach」)。 ↩

  8. 出处:「Building a Responsive Website from Scratch」第 447–471 段(text/19-fm-introduction.txt:449,搜「trim()」;:453,搜「You must write something」;:463,搜「Prevent duplicates」)。 ↩

  9. 出处:「Building a Responsive Website from Scratch」第 479–511 段(text/19-fm-introduction.txt:483,搜「classList.toggle」;:489,搜「Undo」;:493,搜「tasks.map」)。 ↩

  10. 出处:「Building a Responsive Website from Scratch」第 517–535 段(text/19-fm-introduction.txt:525,搜「filter(task =>」;:521,搜「li.remove()」)。 ↩

  11. 出处:「Building a Responsive Website from Scratch」第 415–439 段(text/19-fm-introduction.txt:419,搜「innerHTML」;:425,搜「completed ? "Undo" : "Done"」)。 ↩

  12. 出处:「Building a Responsive Website from Scratch」第 377–381 段(text/19-fm-introduction.txt:377,搜「DOMContentLoaded」;:379,搜「HTML is completely parsed」)。 ↩↩↩

  13. 出处:「Building a Responsive Website from Scratch」第 551–571 段(text/19-fm-introduction.txt:551,搜「addEventListener("click"」;:555,搜「parentElement.parentElement」;:565,搜「event delegation」)。 ↩↩

  14. 出处:「Building a Responsive Website from Scratch」第 579–649 段(text/19-fm-introduction.txt:577,搜「Functional testing」;:609,搜「User interface testing」;:625,搜「Responsive design testing」;:639,搜「Cross-browser compatibility」)。 ↩

  15. 出处:「Building a Responsive Website from Scratch」第 637 段(text/19-fm-introduction.txt:637,搜「prevents unusable narrow views」)。 ↩

  16. 出处:「Building a Responsive Website from Scratch」第 661–671 段(text/19-fm-introduction.txt:667,搜「myInpoot」;:663,搜「nothing happens」)。 ↩↩

  17. 出处:「Building a Responsive Website from Scratch」第 163–349 段(text/19-fm-introduction.txt:165,搜「#f44336」;:175,搜「max-width: 400px」;:271,搜「rgba(0, 0, 0, 0.1)」;:275,搜「justify-content: space-between」;:297,搜「#2ecc71」;:345,搜「#007bff」;:229,搜「transition: 0.3s」)。 ↩

  18. 出处:「Building a Responsive Website from Scratch」第 581–607 段(text/19-fm-introduction.txt:581,搜「Add task:」;:585,搜「Ensure the input field clears」;:603,搜「Local storage persistence」)。 ↩

  19. 出处:「Building a Responsive Website from Scratch」第 653 段(text/19-fm-introduction.txt:653,搜「Debugging is the process」)。原文:「Debugging is not just about fixing code; it is about understanding what the code is doing at runtime and comparing that to what it should be doing.」 ↩↩

  20. 出处:「Building a Responsive Website from Scratch」第 657 段(text/19-fm-introduction.txt:657,搜「detective」)。原文:「Think of it as being a detective: the browser gives you clues... fix the root cause, not just the symptom.」 ↩

  21. 出处:「Building a Responsive Website from Scratch」第 565 段(text/19-fm-introduction.txt:565,搜「event delegation」)。原文:「Instead of adding separate event listeners to each individual Done and Remove button, this function uses event delegation... new tasks are continuously being added to the list.」 ↩