一、项目目标与核心能力
我们要实现的功能非常经典:
-
用户输入任务项,点击“+ Add Item”添加到列表;
-
每个任务项可被勾选/取消,表示完成状态;
-
所有数据持久化存储在浏览器的
localStorage中,刷新页面不丢失; -
页面加载时自动恢复历史数据。 而更重要的是——我们拒绝“流程式面条代码” ,转而采用职责分离 + 函数封装 + 函数式风格来组织逻辑。
二、关键知识点回顾
1. CSS 继承与布局细节
在前端开发中,CSS 的**继承(inheritance)**机制是一个基础但容易被误解的概念。很多初学者常常疑惑:“为什么父元素设置了 font-size,子元素却没变?”或者“为什么背景色没有传给子元素?”本文将结合一段实际代码,带你彻底搞懂 CSS 继承的规则。
一个典型例子
来看下面这段 HTML 和 CSS 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS继承</title>
<style>
body {
background-color: green;
}
</style>
</head>
<body>
<p>Hello world</p>
<div style="overflow:hidden; font-size: 28px; height: 300px; background-color: yellow; color: pink;">
你好
<p style="background-color: red; height: inherit;">大唐诡事录</p>
</div>
<div>1111</div>
</body>
</html>
观察现象:
<div>设置了font-size: 28px和color: pink,其内部的<p>元素自动继承了这些样式,文字变大且颜色为粉色。- 但
<div>的background-color: yellow并没有传递给内部的<p>—— 它显示的是自己设置的红色背景。 <p>使用了height: inherit,所以它的高度继承了父元素的300px。
什么是 CSS 继承?
CSS 继承是指:某些 CSS 属性会自动从父元素传递给子元素,而无需显式声明。这种机制减少了重复代码,让样式更易维护。
✅ 会继承的常见属性(通常与“文本”相关):
colorfont-familyfont-sizefont-weightline-heighttext-alignvisibilityletter-spacingword-spacing
这些属性天然具有“上下文连续性”,比如一段文字的字体大小和颜色,通常希望在整个段落或区块内保持一致。
❌ 不会继承的常见属性(通常与“布局/盒模型”相关):
background-colorwidth/heightmargin/padding/borderdisplaypositionfloatoverflow
这些属性控制的是元素自身的几何结构或视觉边界,如果自动继承反而会造成混乱。
💡 小技巧:你可以通过浏览器开发者工具查看某个属性是否继承——如果子元素未设置该属性,但样式面板中显示为“inherited from...”,那就是继承来的。
强制继承:inherit 关键字
即使某个属性默认不继承,你也可以手动强制继承父元素的值,使用 inherit:
.child {
background-color: inherit; /* 强制继承父元素背景色 */
height: inherit; /* 如示例中的 p 标签 */
}
在上面的例子中,<p style="height: inherit;"> 正是利用这一点,让自己的高度等于父 div 的 300px。
2. localStorage:前端的“本地数据库”
- 浏览器提供的永久性存储空间(除非用户手动清除);
- 以 key-value 形式存储字符串,因此存对象前需用
JSON.stringify(),取回后用JSON.parse(); - 容量通常为 5~10MB,足够应对大多数轻量级应用。
⚠️ 注意:不要存储敏感信息(如密码),因为 localStorage 对 XSS 攻击无防护。
三、代码实现:从“能跑”到“高级”
1. HTML 结构(简洁语义化)
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items">
<input type="text" name="item" placeholder="Item Name" required />
<input type="submit" value="+ Add Item" />
</form>
</div>
使用 <ul> 表示列表,<form> 处理提交,语义清晰,利于无障碍访问和 SEO。
2. JavaScript 核心逻辑(函数式封装)
✅ 第一步:获取 DOM 与初始化数据
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const items = JSON.parse(localStorage.getItem('todos')) || [];
这里直接从 localStorage 读取历史数据,若无则默认空数组。一行代码完成初始化,干净利落。
✅ 第二步:封装渲染函数 —— populateList
工作十年的老程序员强调:“超过10行的流程代码,一定要封装成函数! ”
function populateList(plates = [], platesList) {
platesList.innerHTML = plates.map((plate, i) => `
<li>
<input
type="checkbox"
data-index="${i}"
id="item${i}"
${plate.done ? 'checked' : ''}
/>
<label for="item${i}">${plate.text}</label>
</li>
`).join('');
}
- 使用
map+ 模板字符串生成 HTML 片段,是典型的函数式风格; - 函数接收数据和容器,职责单一,不关心数据来源;
- 默认参数
plates = []防止传入undefined导致报错; - 调用者只需
populateList(items, itemsList),无需知道内部如何拼接。
这就是“封装”的魅力:调用的人只关心“做什么”,不关心“怎么做” 。
✅ 第三步:添加任务 —— addItem
function addItem(event) {
event.preventDefault();
const input = this.querySelector('[name=item]');
const text = input.value.trim();
if (text) {
items.push({ text, done: false });
localStorage.setItem('todos', JSON.stringify(items));
populateList(items, itemsList);
this.reset(); // 清空表单
}
}
- 利用
this指向绑定的<form>元素,精准获取输入值; trim()防止用户输入纯空格;- 数据变更后立即持久化,并重新渲染。
✅ 第四步:切换完成状态 —— toggleDone
function toggleDone(event) {
if (event.target.matches('input[type="checkbox"]')) {
const index = event.target.dataset.index;
items[index].done = !items[index].done;
localStorage.setItem('todos', JSON.stringify(items));
populateList(items, itemsList);
}
}
- 使用事件委托(监听
<ul>而非每个<input>),性能更优; matches()比tagName === 'INPUT'更健壮,避免误判其他 input 类型;- 同样遵循“修改数据 → 存储 → 重渲染”流程。
✅ 第五步:绑定事件 & 初始化渲染
addItems.addEventListener('submit', addItem);
itemsList.addEventListener('click', toggleDone);
populateList(items, itemsList); // 首次加载
整个脚本没有一行冗余逻辑,每个函数都短小精悍、意图明确。
四、为什么这样做更“高级”?
| 对比维度 | 流程式写法 | 封装 + 函数式写法 |
|---|---|---|
| 可读性 | 逻辑混杂,难以快速理解 | 每个函数职责清晰 |
| 可维护性 | 修改一处可能影响全局 | 局部修改,风险可控 |
| 可复用性 | 几乎无法复用 | populateList 可用于任何列表 |
| 调试难度 | 需逐行排查 | 可单独测试每个函数 |
| “逼格”指数 | 🌱 | 💎💎💎 |
提升代码逼格,不是炫技,而是对工程负责。
五、总结与延伸
通过这个小小的 Todo 应用,我们实践了:
- 如何用
localStorage实现数据持久化; - 如何用函数式思维替代冗长的流程代码;
- 如何通过封装提升代码的抽象层级;
- 如何关注 CSS 和 JS 中的“魔鬼细节”。