首页
/ 深度解析 freeCodeCamp "Comment out HTML" 挑战:用 HTML 注释临时隐藏页面元素

深度解析 freeCodeCamp "Comment out HTML" 挑战:用 HTML 注释临时隐藏页面元素

2026-09-06 19:15:21作者:邓越浪Henry

本指南围绕 freeCodeCamp 开源课程中 Responsive Web Design 认证的 "Comment out HTML"(注释 HTML)挑战展开,逐层讲解 HTML 注释语法 <!-- --> 的用途、"只注释 h1p、保留 h2 可见"的任务拆解方式、seed 初始代码的陷阱,以及挑战自带 5 条自动化测试断言的判定逻辑。读完本文,你既能顺利通过该关卡,也能理解 freeCodeCamp 挑战文件(Markdown + front matter)的编写规范、challengeType 的语义,以及隐藏在其背后的 DOM 断言机制。

一、挑战在课程地图中的位置

该挑战的源文件位于 curriculum/challenges/english/blocks/basic-html-and-html5/bad87fee1348bd9aedf08804.md,属于 Basic HTML and HTML5 区块(block)。从仓库的区块结构文件 curriculum/structure/blocks/basic-html-and-html5.json 可以看到其完整编排顺序,它处在一个刻意设计的教学序列中:

  1. Say Hello to HTML Elements —— 认识第一个 HTML 元素;
  2. Headline with the h2 Element —— 标题元素;
  3. Inform with the Paragraph Element —— 段落元素;
  4. Fill in the Blank with Placeholder Text —— 占位文本;
  5. Uncomment HTMLbad87fee1348bd9aedf08802.md)—— 反注释,让代码生效;
  6. Comment out HTML(本挑战)—— 正注释,让代码失效;
  7. Delete HTML Elements —— 直接删除元素。

也就是说,freeCodeCamp 先教你用注释“隐藏”元素,再教你“取消注释”让它们恢复,最后才进入“删除元素”,通过注释与删除的对照,让学习者理解两者的区别:注释是临时性停用代码,而删除是永久移除代码。整个区块又隶属于 curriculum/structure/superblocks/responsive-web-design.json 中的 Responsive Web Design 认证路径,是零基础学员接触 HTML 后最早遇到的几个关卡之一。

二、HTML 注释语法:<!---->

挑战的 # --description-- 部分直接点明核心语法:

Remember that in order to start a comment, you need to use <!-- and to end a comment, you need to use -->

HTML 注释由起始定界符 <!--结束定界符 --> 包裹,介于两者之间的全部内容都不会被浏览器解析和渲染。注意该语法与多语言注释的差异:

  • HTML 注释不支持嵌套:第一个 --> 就会结束注释;
  • 注释定界符必须成对出现,只写 <!-- 而不写 --> 会把后面整段代码都“吞掉”;
  • 注释内的文本(包括其中的 HTML 标签)对用户不可见,但会原样保留在源代码中,方便后续恢复。

正是利用“注释内容不渲染、但代码仍保留在源文件中”这两个特性,开发者在日常工作中常用注释来:

  • 给后续维护者留说明文字;
  • 临时屏蔽某段功能代码,而无需删除它(方便调试与回退);
  • 在页面开发初期占位。

本挑战前面的 "Uncomment HTML" 已给出定义:Commenting is a way that you can leave comments for other developers within your code without affecting the resulting output that is displayed to the end user. 同时它也是一条“让代码暂时不生效的捷径”:make code inactive without having to delete it entirely(见 Uncomment HTML 挑战源文件)。而本挑战则是从“取消注释”反过来,练习“把代码注释掉”。

三、任务拆解:到底要做什么

挑战的 # --instructions-- 是一句非常具体、也暗含陷阱的指令:

Comment out your h1 element and your p element, but not your h2 element.

即:h1p 从页面上“消失”,但 h2 必须保持可见。

初看很简单,难点在于 seed(初始代码)的形态。挑战给出的初始 HTML 是这样的:

<!--
<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>
-->

这里存在一个非常典型的“坑”:三个元素被一条巨大的注释整体包裹。此时页面上什么都看不见。而任务要求的是“注释掉 h1p、但不要注释 h2”。因此你不能简单地保留这一条大注释,而必须把这条注释拆解、重排为三条互不干扰的独立片段:

  • <h1> 之前开始注释、并在 <h1> 之后立刻用 --> 结束,只包裹住 h1
  • <h2> 独立暴露在外,不被任何 <!-- / --> 包裹;
  • <p> 再用一对 <!-- / --> 单独包裹。

description 里特意强调:Here you'll need to end the comment before your h2 element begins.(你需要在 h2 元素开始之前结束注释)——这正是提示你要放弃整块注释、改为分段注释的核心暗示。

四、参考解法:逐行还原思路

挑战文件的 # --solutions-- 部分给出了官方参考答案:

<!--<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> -->

逐行拆解这套解法:

  1. <!--<h1>Hello World</h1>-->h1 两侧分别紧跟 <!---->,它被整体注释,不再出现在渲染结果中;
  2. <h2>CatPhotoApp</h2>:完全裸露、无注释包裹,正常渲染为页面标题;
  3. <!--<p>...</p> -->p 段落被独立注释(注意结束定界符前多了一个空格,这在语法上完全合法,注释中多余空白会被忽略)。

观察一个细节:注释定界符既可以独占一行(如 seed 中的写法),也可以与标签同处一行(如 solution 中的写法)。HTML 解析器只关心 <!----> 的位置,不关心周围有没有空白或换行。理解这一点后,把整块注释“切割”成三个独立单元就不难了。

如果不想照抄官方解法,只要满足“两个被注释、一个不被注释”即可,例如下面这种保留换行的写法同样正确:

<!--
<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>
-->

五、读懂 5 条自动化测试断言

freeCodeCamp 的每个挑战都内置 # --hints-- 自动化测试,只有全部通过才算过关。本挑战共有 5 条断言,全部直接写在源文件中,我们来逐条解析它们的判定逻辑(这些断言使用 Chai 断言库风格,并在浏览器上下文中对“你提交后的代码”求值):

1. h1 必须被注释掉

assert.isEmpty(document.querySelectorAll('h1'));

document.querySelectorAll('h1') 会查询最终渲染后的 DOM。一旦 h1 位于 <!-- --> 之内,浏览器就不会把它构建进 DOM,查询结果为空集合(NodeList 长度为 0),assert.isEmpty 通过。反过来说,只要代码里残留着未被注释的 <h1>,这条断言就会失败。

2. h2 必须保持可见

assert.isNotEmpty(document.querySelectorAll('h2'));

与上一条正好相反:h2 必须真实存在于 DOM 中,querySelectorAll 返回非空集合,断言才通过。这条测试确保了“but not your h2 element”那半句要求被落实

3. p 必须被注释掉

assert.isEmpty(document.querySelectorAll('p'));

逻辑与 h1 完全一致:p 也不能出现在渲染后的 DOM 中。

4. 每一条注释都必须正确闭合

assert.isAbove(code.match(/[^fc]-->/g).length, 1);

这是唯一一条针对源代码文本而非 DOM 的断言。它用正则 /[^fc]-->/g 在代码字符串中查找 --> 出现的次数,并要求大于 1。之所以排除前面紧跟字母 fc-->(负向字符类 [^fc]),是为了防止把 <!-- 起始定界符自身的那部分错误计数。它从正面保证:存在至少两处--> 完成的注释闭合(对应 h1p 两条独立注释),从而间接验证注释结构是“成对、闭合、分段”的。

5. h1h2p 的书写顺序不许改变

assert.strictEqual(code.match(/<([a-z0-9]){1,2}>/g)[0],'<h1>');
assert.strictEqual(code.match(/<([a-z0-9]){1,2}>/g)[1],'<h2>');
assert.strictEqual(code.match(/<([a-z0-9]){1,2}>/g)[2],'<p>');

这条规则禁止你通过重排元素顺序这种“偷懒方式”来投机取巧(例如把 h2 挪到注释块外面、其余元素丢进注释)。它依次抽取代码中出现的短标签,并断言第一个是 <h1>、第二个是 <h2>、第三个是 <p>,即标签出现顺序与初始结构一致,你只能通过注释来调整可见性。

综上,本挑战的测试矩阵可归纳为:

断言目标 判定方式 失败示例
h1 被注释 渲染后 DOM 无 h1 忘记加注释或注释未闭合
h2 不被注释 渲染后 DOM 存在 h2 沿用初始的大注释块,把 h2 也包进去
p 被注释 渲染后 DOM 无 p 忘记加注释或注释未闭合
注释闭合完整 源码中 --> 出现 2 次以上 只写 <!-- 不写 -->
元素顺序不变 依次出现 <h1><h2><p> 重排元素顺序

六、底层原理:为什么“注释掉”后元素就不可见

这套测试验证方式其实反映了 HTML 渲染的标准行为:

  1. HTML 源码被浏览器下载后,先经过 tokenizer(词法分析) 阶段。当解析器读到 <!--,会进入注释状态并一直吞掉字符,直到遇见 -->
  2. 被注释的内容不会生成任何 DOM 节点,因此 document.querySelectorAll('h1') 这类 DOM 查询自然查不到被注释的元素;
  3. freeCodeCamp 的测试正是在页面加载完成后、对运行时 DOM 执行查询(对 code 文本的断言除外),所以“注释影响 DOM 结构”这一浏览器机制,恰好成为机器可判定的通关标准。

这也解释了为什么该类挑战把“注释”作为 HTML 的必备基本功:掌握注释就能精确控制“哪些内容进入 DOM、哪些内容留在源码里”,这对调试、协作和渐进式开发都至关重要。相关断言在整个 Basic HTML 区块中被大量复用(同区块多个挑战的 # --hints-- 都使用了 assert.* 家族断言,见 curriculum/challenges/english/blocks/basic-html-and-html5 目录下的各挑战文件)。

七、挑战文件本身:Markdown 与 front matter 的结构

该挑战同时是 freeCodeCamp 课程体系的内容即代码(curriculum-as-code)的典型样本。一个挑战 = 一个 Markdown 文件,用 --- 包裹的 YAML front matter + 若干 # --section-- 语义化区块构成。其 front matter 字段含义如下:

字段 本挑战取值 作用
id bad87fee1348bd9aedf08804 全局唯一标识,被区块结构文件与种子数据引用
title Comment out HTML 挑战标题
challengeType 0 挑战类型编号
videoUrl Scrimba 教学视频地址 关联视频课程(此处仅作外部参考)
forumTopicId 16782 关联论坛讨论主题
dashedName comment-out-html URL 友好的短名称

其中 challengeType: 0 并非随意取值。在 packages/shared/src/config/challenge-types.ts 中,const html = 0 被定义为 HTML 类型挑战;同一文件中的 viewTypes 映射把 html 类型对应为 classic 视图(challenge-types.ts),submitTypes 则把其提交方式对应为 testschallenge-types.ts)——也就是说,这一类挑战会以内置的经典单文件编辑器呈现,并通过运行 # --hints-- 中的测试来判断对错。

这些 front matter 字段并非自由填写,而是受严格 schema 校验约束。在 curriculum/schema/challenge-schema.js 中可以看到:

challengeType: Joi.number().min(0).max(33).required(),

即 challengeType 必须是 0~33 之间的整数且必填,0 正好处于合法区间内。整个课程通过这类 Joi schema(challenge-schema.jscurriculum-schema.jsmeta-schema.js 等)保证上万份挑战文件的格式一致性。

而区块内挑战的先后顺序则由 curriculum/structure/blocks/basic-html-and-html5.jsonchallengeOrder 数组维护——它按 { "id", "title" } 列出全部 28 个挑战的次序,并声明 "helpCategory": "HTML-CSS" 便于课程导航分类。因此,编辑一道挑战的内容只要改对应 .md 文件,而调整其在课程中的位置则需要同步修改区块结构 JSON,二者是分离的。

八、递进式教学:从注释到删除的设计逻辑

把本挑战放进 Basic HTML and HTML5 区块的更大图景中看,能更清楚 freeCodeCamp 的教学思路。第 5 关 "Uncomment HTML" 与本关 "Comment out HTML" 构成一组“镜像练习”:

  • Uncomment HTML:seed 中 h1h2p 全被注释,任务是把三条注释去掉,让三个元素重新可见(其官方解法见 bad87fee1348bd9aedf08802.md# --solutions--);
  • Comment out HTML:seed 中三个元素被一条注释整体隐藏,任务变成有选择地注释其中两个、保留一个。

二者共同训练“开/关”代码的能力;紧接着的 Delete HTML Elements 则更进一步,教学习者如何彻底移除元素。三个关卡连起来,恰好覆盖了代码生命周期管理的三种动作:注释(临时禁用)→ 反注释(恢复启用)→ 删除(永久移除)。从 “Hello World” 的页面骨架(h1/h2/p)反复出现在这些早期关卡中可以看出,它们共用同一段 CatPhotoApp 示例文案,属于刻意设计的“脚手架课程”,让学员把注意力完全集中在单一新语法上。

九、常见错误与自检清单

通关前,对照以下检查点自查,可避免最常见的翻车原因:

  1. 没有拆开初始大注释:如果你仍保留 seed 中那个同时包裹三个元素的 <!-- ... -->h2 也会被隐藏,第 2 条断言必然失败。务必在 h1 结束后先写 -->
  2. 只写了 <!-- 忘了 -->:会出现“注释吞掉后续所有标签”的情况,第 4 条断言(--> 出现次数)会失败;
  3. 删除了元素而不是注释元素:任务要求注释而非删除;虽然两种方式都能让元素“不可见”,但第 5 条关于标签顺序的断言会因缺少标签而失败;
  4. 调整了 h1/h2/p 的相对顺序:即使结果“看起来对”,也会被第 5 条断言拦截;
  5. h2 内部或跨元素写注释:请确认每条注释都完整包住目标元素、且不波及相邻的 h2

用一句话概括本挑战的成功标准:源码中保留 <h1><p> 的完整标签但用注释包裹它们,同时让 <h2> 保持裸奔状态,且不改变三者出现顺序。 掌握这条规则,你不仅能顺利通关,也真正理解了 HTML 注释的语法边界与它在页面渲染中的角色。

登录后查看全文
热门项目推荐
相关项目推荐