从零造一个网站 — 待办清单应用的完整流程¶
这一章讲一件事:把前 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)整体重建。
- 先定功能清单,再写一行代码;草图是界面验收的合同12;
- 三文件工程(index.html / styles.css / script.js)是前端的「三权分立」;
*{box-sizing:border-box}开局;简单响应式 = flexbox 弹性 +min-width保底,复杂才上媒体查询4;- localStorage 只存字符串:存 stringify、取 parse、null 兜底
|| []56; - 五动作闭环(加载/新增/切换/删除/保存)+ 一个造元素的「工厂函数」,结构不散;
DOMContentLoaded保证脚本运行时 DOM 已就绪12;事件委托管动态元素13;- 测试四类:功能、界面、响应式、跨浏览器;「刷新还原」和「拼写」都要单列;
- 调试动线:Console 看报错 → Sources 跳行 → Elements 核对 → 修根因19;
- 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」) |
-
出处:「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。 ↩↩ -
出处:「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 删除)。 ↩↩ -
出处:「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」)。 ↩ -
出处:「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」)。 ↩↩ -
出处:「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.」 ↩↩↩ -
出处:「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.」 ↩↩↩ -
出处:「Building a Responsive Website from Scratch」第 389–409 段(
text/19-fm-introduction.txt:405,搜「forEach」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 447–471 段(
text/19-fm-introduction.txt:449,搜「trim()」;:453,搜「You must write something」;:463,搜「Prevent duplicates」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 479–511 段(
text/19-fm-introduction.txt:483,搜「classList.toggle」;:489,搜「Undo」;:493,搜「tasks.map」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 517–535 段(
text/19-fm-introduction.txt:525,搜「filter(task =>」;:521,搜「li.remove()」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 415–439 段(
text/19-fm-introduction.txt:419,搜「innerHTML」;:425,搜「completed ? "Undo" : "Done"」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 377–381 段(
text/19-fm-introduction.txt:377,搜「DOMContentLoaded」;:379,搜「HTML is completely parsed」)。 ↩↩↩ -
出处:「Building a Responsive Website from Scratch」第 551–571 段(
text/19-fm-introduction.txt:551,搜「addEventListener("click"」;:555,搜「parentElement.parentElement」;:565,搜「event delegation」)。 ↩↩ -
出处:「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」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 637 段(
text/19-fm-introduction.txt:637,搜「prevents unusable narrow views」)。 ↩ -
出处:「Building a Responsive Website from Scratch」第 661–671 段(
text/19-fm-introduction.txt:667,搜「myInpoot」;:663,搜「nothing happens」)。 ↩↩ -
出处:「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」)。 ↩ -
出处:「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」)。 ↩ -
出处:「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.」 ↩↩ -
出处:「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.」 ↩ -
出处:「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.」 ↩