freeCodeCamp "Delete HTML Elements" 挑战拆解:从删除 `<h1>` 到课程自动判题机制
freeCodeCamp 的 "Basic HTML and HTML5" 课程通过逐个小练习引导学习者搭建 CatPhotoApp(猫咪照片应用),其中 Delete HTML Elements 挑战 教给学习者的核心技能是:从 HTML 文档中准确删除一个元素,同时保证其余元素不被破坏。本文以该挑战文件为骨架,结合本仓库(freeCodeCamp 开源课程库)的源码与结构文件,逐行拆解任务、起始代码、自动判题规则以及挑战文档的前置元数据格式,帮助读者既掌握 HTML 编辑基本功,也理解 freeCodeCamp 课程体系 "一段 Markdown 即一道题" 的实现方式。
挑战背景:为什么要"删掉一个元素"?
这道题的场景设定非常朴素:在构建 CatPhotoApp 的界面上,竖向空间(vertical space)有限,需要移除暂时用不上的元素,为后续课程逐步添加图片、链接、表单等内容腾出干净的区域。
"删除元素"看似是一个"反向操作",但它与"添加元素"同等重要:
- 前端页面最终呈现给用户的内容由当前代码中实际存在的元素决定,多余的标签会直接占用布局与视觉空间;
- 只有学会精确删除(而不是连标签带内容一起误删),才能保证页面上其他元素继续正常渲染;
- 删除这一动作天然要求学习者理解"标签成对出现、内容夹在开标签与闭标签之间"这一 HTML 基本结构。
在课程叙事里,这道挑战处于 Basic HTML and HTML5 的早期阶段。根据 basic-html-and-html5.json 结构文件,Delete HTML Elements(id 为 bad87fed1348bd9aedf08833)排在 "Comment out HTML" 之后、位于整个 block 的第 7 个挑战,前后衔接的是注释、HTML5 元素、图片与链接等系列练习,整体难度定位为纯新手入门。
任务指令与起始代码
挑战的 instructions 只有一句话:
Delete your
h1element so we can simplify our view.
翻译过来即:删除页面上的 <h1> 元素,让视图更简洁。注意任务要求的是删除整个 h1 元素(包括开标签、闭标签与其文本内容),而不是删除标题中的文字或把内容清空。
题目附带的种子代码(seed)如下,学习者将基于它开始编辑:
<h1>Hello World</h1>
<h2>CatPhotoApp</h2>
<p>Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.</p>
起始页面中同时存在三个元素:
| 元素 | 内容 | 在本挑战中的归宿 |
|---|---|---|
<h1>Hello World</h1> |
一级标题 | 删除 |
<h2>CatPhotoApp</h2> |
二级标题(应用名) | 保留 |
<p>Kitty ipsum …</p> |
段落文字(占位文本) | 保留 |
之所以保留 h2 而不是 h1,是因为 CatPhotoApp 作为一个应用页面,其主标题使用 h2 展示即可,页面层级更符合后续课程逐步填充的真实内容形态。
参考解法:删除后应得到什么结果?
挑战自带的 --solutions-- 段落给出了标准答案。删除 h1 元素后,代码应为:
<h2>CatPhotoApp</h2><p>Kitty ipsum dolor sit amet, shed everywhere shed everywhere stretching attack your ankles chase the red dot, hairball run catnip eat the grass sniff.</p>
也就是说,只需要把种子代码里的 <h1>Hello World</h1> 连同它前后的空行一并移除,剩余的 h2 与 p 元素保持原样即可通过。HTML 对空白(空行、空格)并不敏感,因此即使两行合并书写,渲染结果也一致。
自动判题规则:三个断言如何检查你的代码?
freeCodeCamp 的挑战会在学习者点击提交后,在浏览器中用一段 --hints-- 里定义的 JavaScript 正则断言逐条验证代码。这道题共包含三条 hints,对应仓库 schema 中"每条挑战必须声明 tests(含 text 与 testString)"的要求(见 challenge-schema.js)。
第一条断言检查 h1 是否被删除:
assert.notMatch(code,/<h1>/gi);
assert.notMatch(code,/<\/h1>/gi);
assert.notMatch 表示"源代码中不得匹配到该正则"。两个正则分别针对开标签 <h1> 与闭标签 </h1>(注意闭标签中的反斜杠转义),g 为全局匹配、i 为忽略大小写。也就是说,只要代码里还残留任何一个 h1 标签(无论写成 <H1> 还是 <h1>),这一条就会失败。
第二条断言检查 h2 仍然存在:
assert.match(code,/<h2>[\w\W]*<\/h2>/gi);
[\w\W] 是一个常见技巧:\w 匹配单词字符,\W 匹配非单词字符,两者取并集等价于"匹配任意字符(包括换行)",因此 [\w\W]* 表示"从开标签到闭标签之间的任意内容,允许为空或跨越多行"。这条规则要求页面中仍有一对完整的 h2 标签。
第三条断言以相同方式检查 p 元素仍然在页面上:
assert.match(code,/<p>[\w\W]*<\/p>/gi);
从这三条规则可以提炼出这道题唯一的通过条件:
- 代码中不存在任何
<h1>/</h1>; - 代码中存在一对完整的
<h2>...</h2>; - 代码中存在一对完整的
<p>...</p>。
值得注意的是,正则断言只检查源码文本,并不真正渲染页面;这意味着只要标签配对完整即可,标签间的文本写什么并不影响判题结果。这也是初学者可以刻意做的"边界验证":把 h2 内容改成任意英文单词,挑战依旧会通过。
挑战文件的完整元数据:一段 Markdown 如何成为一道题?
仓库中每个挑战都是一个独立的 Markdown 文件,文件名即挑战的全局唯一 id。本挑战对应文件为 bad87fed1348bd9aedf08833.md,位于 curriculum/challenges/english/blocks/basic-html-and-html5/ 目录下。文件开头以 YAML frontmatter 声明元数据:
---
id: bad87fed1348bd9aedf08833
title: Delete HTML Elements
challengeType: 0
videoUrl: 'https://scrimba.com/p/pVMPUv/ckK73C9'
forumTopicId: 17559
dashedName: delete-html-elements
---
结合仓库的 challenge-schema.js 可以逐项理解其约束与含义:
id:挑战全局唯一标识,schema 要求符合 MongoDB ObjectId 格式(Joi.objectId().required()),它同时决定 Markdown 文件名与课程结构文件中的引用关系;title:挑战的展示标题,必填(Joi.string().required());challengeType:挑战类型编号,schema 规定取值范围为 0–33(Joi.number().min(0).max(33).required())。这里取0,属于 freeCodeCamp 沿用自早期课程的传统代码练习类型,该类型常量在 packages/shared/src/config/challenge-types.ts 中统一维护;videoUrl:可选的讲解视频地址,schema 中允许为空字符串(Joi.string().allow('')),该字段仅在题目提供配套视频讲解时填充;forumTopicId:关联到社区论坛讨论帖的编号,类型为数值(Joi.number());dashedName:小写、用连字符连接的语义化名称(delete-html-elements),schema 以slugRE正则校验,主要用于路由与检索。
frontmatter 之后,挑战内容按 # --description--、# --instructions--、# --hints--、# --seed--、# --solutions-- 等带 -- 包裹的分节标记划分,课程构建工具据此解析出描述、指令、判题规则、种子代码与标准解法。description 在 schema 中被规定为必填字符串(challenge-schema.js),而 solutions 则要求为至少含一个文件的数组(challenge-schema.js)。此外该 block 还通过 basic-html-and-html5.json 中的 challengeOrder 数组维护挑战顺序,blockLayout 被标记为 legacy-challenge-list,表明这是沿用早期列表式布局的传统课程块。
排错自查:提交总失败时该检查什么?
如果挑战无法通过,90% 的情况源于以下三种:
- 误删了
h2或p:题目只要求删除h1,任何多余删除都会触发对应断言失败。恢复方式是把起始代码中需要的行重新粘贴回来。 h1标签仍残留一半:例如只删了<h1>Hello World</h1>却留下空标签,或误删了某个标签的一部分。由于notMatch分别匹配开标签与闭标签,残留任意一半都会失败,需整段清除。- 复制粘贴引入多余的
h1:例如在编辑器中保留了旧版本又粘贴了新版本,代码中出现两份内容时,需留意是否遗留了第二处h1。
一个实用的自查方法:对照上文三条断言手工扫描代码——先确认没有 h1 相关文本,再确认 h2 与 p 各有一对完整的开闭标签。
延伸思考:删号是"减法",也是后续构建的基础
这道题虽然只涉及删除一个元素,却为后续课程埋下了两处关键伏笔:其一,同一 block 后续的 "Introduction to HTML5 Elements" 将引入 main、header、footer 等语义化标签,学习者在动手"删减"时已熟悉"标签成对存在、内容位于其间"的语法直觉;其二,该练习所在的 CatPhotoApp 叙事会一路延续到图片(Add Images to Your Website)、链接(Link to External Pages with Anchor Elements)、列表与表单等十余道挑战,最终组合成完整的猫咪照片应用页面。
因此,把这道挑战当作"纯删除练习"并不完整——它真正训练的是学习者在既有代码中精确定位目标、最小化修改、并通过自动断言验证结果的能力。理解这一点,也就理解了 freeCodeCamp 课程"以微小的可验证步骤渐进构建完整项目"的教学设计:每一步的成败都通过仓库 schema 中强制定义的 tests 数组即时反馈,让新手在第一次接触 HTML 时就能建立"写完即校验、校验即闭环"的工程习惯。若想继续深挖,可对照阅读本仓库中同目录下的其余挑战文件(如 "Comment out HTML" 与 "Uncomment HTML"),对比它们如何通过注释语法执行类似的"增删"训练。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00