freeCodeCamp 课程挑战深度解析:用<ul>与<li>创建无序列表
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
导读
本文以 freeCodeCamp 开源课程中 Basic HTML 与 HTML5 模块的经典挑战「Create a Bulleted Unordered List」为主线,逐层拆解无序列表(<ul>/<li>)的 HTML 语法、该挑战在课程体系中的位置、其 Markdown 源文件的完整结构,以及挑战内置的自动化测试与判定逻辑。读完本文,你不仅能掌握在 freeCodeCamp 课程中编写、提交与通过这类 HTML 练习的完整方法,还能了解这些挑战文件在 curriculum 目录中的组织方式与校验机制,为进一步参与课程建设打下基础。
挑战在课程中的定位:Basic HTML 与 HTML5 模块
本挑战的源文件位于课程树中:
curriculum/challenges/english/blocks/basic-html-and-html5/bad87fee1348bd9aedf08827.md
文件名中的bad87fee1348bd9aedf08827是该挑战的唯一id,贯穿课程的排序、测试与用户学习进度记录。从课程的「块结构清单」curriculum/structure/blocks/basic-html-and-html5.json 可以看到,本挑战在整个模块的 28 个挑战中排在第 15 位(索引 14),紧跟「Turn an Image into a Link」之后,下一个挑战正是「Create an Ordered List」。
id: bad87fee1348bd9aedf08827 title: Create a Bulleted Unordered List challengeType: 0 dashedName: create-a-bulleted-unordered-list这一位置并非偶然:学习者此前已掌握h2标题、p段落、img图片、a链接等基础元素,本挑战开始引入「列表」这类承载多行内容的结构化语义元素。此前的所有练习都在 CatPhotoApp 这个小猫照片应用页面中进行,通过不断追加页面元素,让学习者体会真实 HTML 文档的渐进式搭建过程。
核心知识点:无序列表的基本语法
挑战的--description--部分(即学习者在页面上看到的概念讲解)给出了无序列表的权威定义:
- HTML 提供专门用于创建无序列表(
<dfn>unordered lists</dfn>,即项目符号式列表)的元素; - 无序列表以起始标签
<ul>开头,随后跟随任意数量的列表项<li>元素; - 最后以
</ul>闭合。
原文档给出了最精简的示例:
<ul> <li>milk</li> <li>cheese</li> </ul>这段代码会在浏览器中渲染出带项目符号的两行内容:milk与cheese。其语义非常明确:ul(Unordered List)表达「一组彼此并列、顺序无关紧要的项目」,而li(List Item)则是其中单个条目。正因如此,无序列表非常适合表示购物清单、特性列表、菜单导航这类内容。
值得深入理解的是默认渲染规则:
- 每个
<li>默认独占一行,浏览器自动在其前方渲染实心圆点项目符号(disc); - 列表项可以包含任意合法的内联或块级内容(如文本、链接、图片),项目符号会自动附着在每个
li块之前; - 若需要为嵌套列表表达层级,可在
li内部再次书写完整的ul或ol,浏览器会自动切换符号样式并缩进; - 列表属于块级语义结构,与
p段落一样参与文档流布局。
挑战任务与起始代码(Seed)
--instructions--对本挑战提出的任务只有一句话:
删除页面底部的最后两个
p元素,并在页面底部创建一个包含三项「猫咪喜爱之物」的无序列表。
对应地,--seed--中给出了一个不完整的 CatPhotoApp 页面作为编辑起点(挑战中的「起始代码」):
<h2>CatPhotoApp</h2> <main> <p>Click here to view more <a href="#">cat photos</a>.</p> <a href="#"><img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a> <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> <p>Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.</p> </main>任务的两个动作分别考查两种能力:
- 删除元素:移除最后两个大段绕口令式
p段落。这是对此前「Delete HTML Elements」挑战所学的巩固; - 新增结构化元素:在
</main>之前补上<ul>及其三个<li>,考查对本次新知识的即时运用。
需要注意任务细节——起始页面已经使用了<main>(HTML5 语义化标签),新列表要放在<main>内、位于整页最底部,以符合「at the bottom of the page」的要求。
内置自动化测试逐条解读(Hints)
每个 freeCodeCamp 挑战都会在--hints--中声明多条测试,浏览器端实时运行并判定答案是否通过。本挑战共有 5 条 hint,分别从结构存在性、数量、标签闭合三个维度验证,非常适合用来理解课程的判定思路。
1. 必须创建一个ul元素
assert.isNotEmpty(document.querySelectorAll('ul'));使用document.querySelectorAll('ul')抓取文档中全部ul元素,assert.isNotEmpty要求结果非空,即页面中至少出现一个无序列表。
2.ul中必须有三个li
assert.lengthOf(document.querySelectorAll('ul li'), 3);querySelectorAll('ul li')选取所有「祖先为ul的li」。注意这里的后代选择器写法排除了嵌套在ol等其它列表容器内的li,精确匹配题目对三个列表项的要求。
3.ul必须成对闭合
assert.match(code,/<\/ul>/gi); assert.match(code,/<ul/gi); assert.strictEqual(code.match(/<\/ul>/gi).length,code.match(/<ul/gi).length);这条 hint 直接对学习者的源代码字符串(code变量)做正则匹配:既要求存在</ul>,又要求<ul与</ul>的数量严格相等。/gi中的g表示全局搜索,i忽略大小写。由于match在无匹配时返回null,前两个断言保证了数量至少为 1,第三个断言则防止出现漏写闭合标签的情况。
4. 每个li都必须闭合
assert.match(code,/<\/li>/gi); assert.match(code,/<li[\s>]/gi); assert.strictEqual(code.match(/<\/li>/gi).length,code.match(/<li[\s>]/gi).length);<li[\s>]中的字符组[\s>]是关键细节:它只匹配「li后紧跟空白或>」的写法,避免误将<li>误写成<li...something>之外的拼接形式;同时跳过<link>这类同样以li开头的标签。此 hint 与上一条配合,共同强制学习者养成「开闭标签成对」的严谨习惯。
5.li内容不得为空或纯空白
assert.isEmpty([...document.querySelectorAll('ul li')].filter((item) => item.textContent.trim() === ""));先筛选出所有文本内容trim()后为空的ul li,再断言其集合为空。这阻止了用「空标签凑数量」的偷懒解法,确保每个li都承载真实文字内容(如 milk、cheese、catnip 等)。
从源码层面看,这种「tests数组中text+testString」的结构正是课程体系对每个挑战的硬性要求。校验该结构的 Joi 模式定义在 curriculum/schema/challenge-schema.js:
tests: Joi.array() .items( Joi.object().keys({ id: Joi.string().allow(''), text: Joi.string().required(), testString: Joi.string().allow('').required() }), ... ) .required(),参考答案解析(Solution)
本挑战在--solutions--中提供了官方标准答案:
<h2>CatPhotoApp</h2> <main> <p>Click here to view more <a href="#">cat photos</a>.</p> <a href="#"><img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a> <ul> <li>milk</li> <li>mice</li> <li>catnip</li> </ul> </main>该解法的要点在于:
- 删除了两个填充文本段落,而保留了首段的「cat photos」链接、
img图片与<main>容器——被删的是「最后两个p」,不是所有p; - 在
<main>内部末尾新增<ul>,内含三个非空<li>,内容选择了猫咪喜爱的典型事物(牛奶、老鼠、猫薄荷),与 CatPhotoApp 的主题保持一致; - 所有开闭标签严格成对,完全满足 5 条 hint 的判定条件。
在源文件中,--solutions--是可选的(Joi 模式定义solutions: Joi.array().items(...).min(1)可选),但为 HTML/CSS 类挑战提供官方解,方便学习者在卡关时对照,也为课程翻译与版本维护保留权威参照。
挑战类型与运行机制:从挑战文件到在线编辑器
本挑战 frontmatter 中的challengeType: 0并非随意数字。在 packages/shared/src/config/challenge-types.ts 中,html类型被定义为0:
const html = 0;同一文件里还定义了该类型对应的视图与提交流程:
export const viewTypes = { [html]: 'classic', ... }; export const submitTypes = { [html]: 'tests', ... };也就是说,challengeType: 0的挑战会以「经典(classic)」双栏界面呈现:左侧是说明面板,右侧是内置浏览器与可编辑的 HTML 源码区;提交时走tests流程——即学习者点击运行按钮后,浏览器加载其页面并执行--hints--中声明的全部测试,实时反馈通过或失败状态。
课程侧的验收脚本位于 curriculum/src/test/test-challenges.js,其中可以看到测试字符串被集中提取、交付给独立的代码求值器执行的关键调用链:
const testStrings = tests.map(test => test.testString); const results = await evaluator.evaluate(testStrings, 5000);5000表示单个测试的执行超时上限(毫秒),这从实现层面解释了为什么每条 hint 都应保持轻量、仅做 DOM 断言而不进行耗时计算。对ul与li这类标签的正确性检查,正是在「页面在内存浏览器中渲染完成后,再对 DOM 与源码字符串执行断言」的机制下完成的。
延伸与衔接:紧随其后的有序列表挑战
本挑战的后续练习是「Create an Ordered List」(curriculum/challenges/english/blocks/basic-html-and-html5/bad87fee1348bd9aedf08828.md),它把无序列表延伸为有序列表:
<ol> <li>Garfield</li> <li>Sylvester</li> </ol>该挑战要求学习者在同一页面中同时维护一个Things cats love:无序列表与一个Top 3 things cats hate:有序列表,其 hint 中包含了「同一类列表只能有一个」的校验:
assert.lengthOf(document.querySelectorAll('ul'), 1); assert.lengthOf(document.querySelectorAll('ol'), 1);对比两个相邻挑战可以看到课程编排的匠心:同一个li列表项语法,通过更换容器标签ul/ol实现「项目符号」与「序号」两种截然不同的视觉与语义效果,帮助学习者举一反三。若想系统性回顾本模块前后的 HTML 知识点,可浏览同目录下的其它挑战文件(如删除元素、添加图片、锚点链接、表单与按钮等),或对照 curriculum/structure/blocks/basic-html-and-html5.json 中的完整challengeOrder列表了解全貌。
给学习者的通关要点与给课程贡献者的参考
对学习者的实操建议可归纳为三步检查法:
- 先删后增:严格按指令删除「最后两个
p」,不要误删其他内容,也不要忘记在</main>之前追加内容; - 成对书写:
<ul>与每个<li>都务必书写闭合标签,这是 hint 中最容易被正则拦下的失分点; - 填充真实内容:每个
li都要有可见文本,避免提交空条目。
而对希望为课程贡献新挑战的开发者而言,本文件是理解「经典 HTML 挑战」作者格式的最佳样例:frontmatter 提供元数据(id、title、challengeType、dashedName等),随后依次是--description--(概念讲解)、--instructions--(任务描述)、--hints--(text加testString的断言数组)、--seed--(起始代码)与可选的--solutions--(官方答案)。复制本文件并替换其中各部分内容,再配合 curriculum/schema/challenge-schema.js 中的字段约束自检,即可生成一份格式合法、能被课程工具链识别的新挑战。本挑战中的所有要求都能在--hints--中找到可直接运行的验证代码,这种「文档即数据、测试即约束」的组织方式,正是 freeCodeCamp 课程体系能够支撑大规模多语言协作维护的基石。
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考