首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
三个原生Web练习题:从响应式布局到表单校验的前端基本功
📅 2026/10/15 18:48:09
✍️ 爱科研究院
👁 阅读 3,247
1. 三个练习题的设计初衷写给谁看练什么先说结论这三个WEB练习题不是随便凑出来的而是一套从能写页面到能写应用的递进练习。我带过不少入门的朋友也批改过大量类似的作业发现一个很普遍的问题——很多人拿着教程敲了半天标签会用了、语法认得了但一旦脱离教程独立做一个完整的页面立刻卡壳。这背后的原因不是代码量不够而是练习的结构出了问题练得太散今天写个按钮明天写个轮播图知识点之间没有串起来。所以我设计了这三个练习题目标只有一个用三个由浅入深的小项目把HTML、CSS、JavaScript三个层面的核心技能一次性串起来。它们分别对应练习题一响应式名片卡片主攻HTML语义化与CSS布局解决页面排不好、不会自适应的问题练习题二待办事项清单主攻原生JavaScript的DOM操作、事件处理和本地持久化解决交互写不活、数据存不住的问题练习题三注册表单实时校验主攻表单控件、正则表达式、防抖逻辑和提交拦截解决表单不会校验、数据不会提交的问题。这三个题目看起来简单但每一个都埋了足够深的坑做完之后你对WEB基础的理解会明显不一样。无论你是正在学WEB的初学者还是需要布置作业给学员的带教者都可以参考这套题目和后面的批改视角。需要说明的是整套练习不依赖任何框架不用构建工具就一个HTML文件加一个CSS文件再加一个JS文件。为什么刻意这样安排因为框架和构建工具会掩盖基础问题——你在React里写的列表渲染其实框架帮你做了大部分DOM操作你在Vue里写的v-model其实是双向绑定的语法糖。脱离这些糖衣用原生方式把每一步做一遍才能暴露出真实的理解短板。我自己的经验是把这三个题啃下来的人再去学任何框架都会轻松很多。因为框架解决的是工程效率和复用问题而这三个题解决的是底层原理和基本功问题。基本功不牢框架用起来总有一种知其然不知其所以然的虚空感。2. 练习题一响应式名片卡片——HTML语义化与CSS布局实战2.1 需求清单与验收标准练习题一的题目描述是这样的用纯HTML和CSS实现一张个人名片卡片卡片包含头像区域、姓名、岗位、联系方式、一句个人简介并要求在不同屏幕宽度下自动调整布局。听起来很简单对不对但如果你想做出一个能过关的版本会发现需要考虑的事情远不止写几个div。我给出的验收标准是四条HTML部分必须使用语义化标签不允许出现div套div套div的无脑结构main、section、header、footer这些标签要出现得合理卡片在**手机375px宽、平板768px、桌面1280px**三个视口宽度下都必须表现正常不允许出现横向滚动条桌面端卡片内信息横向排布头像在左文字在右手机端必须纵向堆叠头像在上文字在下不允许使用任何CSS框架也不允许使用JavaScript纯粹用CSS实现自适应。这里最容易被忽略的是第二条。很多人写完CSS之后只在浏览器里拖拽窗口测一下觉得差不多就交上来了。但拖拽窗口是连续的宽度变化你要真想测三个固定断点最好直接用开发者工具的设备模拟逐个宽度检查。尤其要留意375px这个宽度——这是很多旧款手机的宽度也是元素溢出最容易发生的宽度。2.2 从浮动到Grid布局方案选型做响应式布局有很多方案旧时代的float、后来流行的Flexbox、以及更适合整体布局的Grid。这个题我建议使用Grid因为卡片内部结构其实是一个典型的左头像、右信息两栏布局而且在不同断点下要切换成单栏用Grid的grid-template-columns搭配媒体查询代码最清晰。.profile-card { display: grid; grid-template-columns: 120px 1fr; gap: 24px; max-width: 560px; margin: 0 auto; padding: 32px; } media (max-width: 540px) { .profile-card { grid-template-columns: 1fr; text-align: center; } }这套代码的核心逻辑不复杂桌面端左侧固定120px给头像右侧1fr占满剩余空间一旦视口窄于540px就切换成单列。这里1fr值得专门说一句——它是Grid里最有用的单位之一代表剩余空间的一份比width: calc(100% - 120px)这种写法优雅得多。很多人会问为什么不用FlexboxFlexbox当然也可以但如果你用Flexbox需要在媒体查询里修改flex-direction而且两个子元素的宽度控制要靠flex: none和flex: 1来配合代码并不比Grid短。Grid在处理这类规则型布局时声明式的优势很明显——你直接描述两列还是一列不需要关心子元素如何伸缩。我不是说Flexbox没用而是这个场景Grid更贴合。2.3 媒体查询与断点设置别拍脑袋媒体查询的断点是最容易拍脑袋的地方。有人喜欢写media (max-width: 768px)有人喜欢写media (min-width: 600px)其实两种写法都可以但你必须明确自己在用渐进增强还是优雅降级的思路。我建议统一用max-width桌面优先逐步缩小适配手机因为现代WEB开发的默认场景是桌面端访问移动端是需要照顾的场景。反过来如果做移动端优先用min-width也很合理关键是团队内保持一致。对于练习题两种都算对但你必须在注释里说明白自己为什么这么选——这是考察理解深度的地方。还有一个细节图片区域的响应式。很多学生的头像用的是img标签图片原始尺寸可能很大如果不加限制在窄屏下会横向溢出。解决办法是.profile-card img { width: 100%; height: auto; border-radius: 50%; }height: auto至关重要——它保证图片在宽度被压缩时按原始比例缩放不会出现变形。这个知识点几乎每个入门者都会遇到但在作业里能提前写对的人不多因为大多数人默认图片会自己缩放实际上img标签在宽度受限时并不会自动收缩它只会让父容器被撑破。2.4 我批改这类作业时看到的常见错误第一个高频错误滥用position定位。有人为了让头像和简介并排给元素加了float又加了position: relative甚至用absolute手动调像素偏移。结果窗口一窄元素重叠得一塌糊涂。我的建议很直接布局用标准文档流配合Grid/Flexboxposition只用来做真正需要脱离文档流的元素比如角标、遮罩不要在正常布局里用绝对定位到处凑位置。第二个高频错误不设置max-width。卡片内容在超宽屏幕上被拉得特别长一行文字从左到右横跨整个屏幕阅读体验极差。加一个max-width: 560px配合margin: 0 auto问题立刻解决。这个细节能体现你有没有真正观察过成品页面应该长什么样。第三个问题比较隐蔽盒模型混乱。padding加在卡片上width又设置了具体像素值导致卡片的实际总宽度超出了可视区域。如果你的CSS代码里还没有出现过box-sizing: border-box那大概率会踩这个坑。我一般建议在练习项目里统一加上*, *::before, *::after { box-sizing: border-box; }这段代码的意思是让所有元素的width都包含padding和border不再额外向外蹭空间。它是设计这类小项目的一个非常省心的前提。3. 练习题二待办事项清单——DOM操作与事件委托的试金石3.1 需求拆解增删改查背后的知识点练习题二是三个题目里代码量最大的一个也是最能暴露JavaScript基本功的一个。题目要求实现一个待办事项清单用户输入文字后回车添加一条待办每条待办前面有复选框勾选后文字有删除线每条待办右侧有删除按钮点击删除刷新页面后数据不丢失。先别看代码光看需求这里面已经藏了四个核心知识点输入框取值与创建节点读取input.value通过document.createElement创建新的列表项事件监听与回调添加按钮的click事件、输入框的keydown事件用于回车提交DOM的增删改appendChild、removeChild或remove()方法localStorage读写JSON.stringify和JSON.parse实现数组持久化。我见过最快的实现是直接把整个列表项用模板字符串拼接成HTML再赋值给innerHTML几行代码就搞定。这种做法在简单场景下没问题但它有几个隐性缺陷一是每次重新渲染整个列表性能在数据量变大时下降明显二是如果待办内容是由用户输入的直接拼HTML有XSS注入风险用户输入img srcx onerroralert(1)就会被浏览器执行。所以我在作业要求里明确写了必须使用createElement和appendChild逐项创建DOM节点不许用innerHTML拼接渲染列表。这不是老古板而是刻意让你体会创建节点和渲染字符串的本质区别。等你以后操作SVG、Canvas、或者和高频更新的列表打交道时会感激这份基础。3.2 事件委托列表元素绑定的正确姿势这个题最常见的坑是给删除按钮逐个绑定事件。想一下你用一个for循环遍历所有按钮挨个addEventListener。如果一开始只有三条待办那没毛病但用户后续添加了新的待办新按钮上并没有绑过事件所以删除功能失效。这就是动态列表的核心痛点。解决办法有两种一种是添加元素之后顺便绑定事件另一种是事件委托——把点击事件挂到列表容器上利用事件冒泡机制通过event.target判断用户是否点中了删除按钮。listEl.addEventListener(click, function (event) { if (event.target.classList.contains(delete-btn)) { const li event.target.closest(li); li.remove(); saveTodos(); } });这里closest(li)是另一个很有用的API它会从当前元素向上查找最近的匹配选择器的祖先节点。为什么不直接用event.target.parentNode因为如果删除按钮内部还有span之类的子元素点击实际落在span上parentNode可能就不是li了。closest帮你规避了这个层级问题。事件委托的好处是一次绑定永远生效新添加的元素天然有事件处理能力。而且从内存角度说绑定一个容器事件比绑定100个按钮事件要省得多。这个知识点在面试里也经常被问到如果你能在作业里主动用出来并且能解释为什么那基本就是满分理解了。3.3 localStorage持久化为什么必须用JSONlocalStorage只能存字符串这是很多人第一次用就会卡住的地方。你要存一个数组[{id: 1, text: 买水果, done: false}]不能直接localStorage.setItem(todos, todos)因为todos是对象/数组会被转成字符串[object Object]。所以必须走一遍JSON序列化function saveTodos() { localStorage.setItem(todos, JSON.stringify(todos)); } function loadTodos() { const data localStorage.getItem(todos); return data ? JSON.parse(data) : []; }读取端要注意的是JSON.parse可能抛异常——如果用户在调试工具里手动改坏了localStorage的值或者你之前存过别的格式一解析就报错中断。稳健写法是包一层try...catch解析失败就返回空数组至少不能影响页面正常运行。关于数据结构还要说一句最好维护一个由对象组成的数组而不是只存字符串数组。因为对象有扩展空间——以后你想给待办加个截止日期、加个优先级都是加一个字段的事。如果一开始就存纯字符串后面改数据结构等于推翻重写。这就是设计数据比写逻辑更重要的一个很小的体现。3.4 学生作业里的高频Bug我批改这类作业时最常见的Bug有这么几个每次看到都觉得在预料之中第一个是刷新后勾选状态丢失。很多人把done状态只通过CSS类名体现在DOM上比如加了个.completed类却忘了把状态同步到数组里保存的时候只保存了text字段。正确做法每次勾选复选框时要么在事件处理函数里更新对应数组元素的done属性要么在保存前统一从DOM重新采集状态。我推荐前者因为数据源和DOM保持一致程序逻辑更可靠。第二个Bug是Enter键触发两次添加。有人给输入框绑了keydown事件判断回车又给表单绑了submit事件两个事件同时触发了。避免方法是二选一——如果你用了form就只用submit事件配合event.preventDefault()就好如果只是在普通的div里用一个文本框那就用keydown。两者并存是自找麻烦。第三个Bug是删除了所有待办后localStorage里遗留空数组。这不算Bug但会让人困惑localStorage.getItem(todos)返回[]loadTodos解析出来一个空数组功能上没问题但调试时看到存储里面有数据会误以为没删干净。更好的做法是当数组长度为0时直接localStorage.removeItem(todos)保持存储干净。4. 练习题三注册表单实时校验——正则、防抖与提交处理的综合演练4.1 字段设计与校验规则练习题三是一个注册表单的实时校验功能。表单包含四个字段用户名、邮箱、密码、确认密码。要求是用户输入过程中实时给出校验反馈错误信息以红色文字显示在对应字段下方全部合法之前提交按钮不可用点击提交后不刷新页面而是把数据打印到控制台并弹出一个注册成功的提示。字段的校验规则我定得比较细这样才能考出逻辑思维用户名必填3到12个字符只允许字母、数字、下划线正则/^\w{3,12}$/正好匹配邮箱必填需要符合基本格式包含和点号不能在开头点号不能在结尾密码必填至少8位必须同时包含字母和数字确认密码必须和密码完全一致。这个题最大的价值在于它逼着你同时处理多个异步反馈源每个字段都有自己的input事件并且字段之间存在依赖关系确认密码依赖密码邮箱和用户名的错误提示要区分为空和格式错两种状态。没有经过思考的人会把所有校验逻辑塞到一个函数里最后代码乱成一锅粥。我建议的做法是给每个字段写一个独立的校验函数比如validateUsername(value)、validateEmail(value)、validatePassword(value)每个函数返回一个字符串空字符串表示通过非空字符串表示错误信息。然后汇总到一个validateForm()里做全部字段检查。这种拆分方式的核心价值是以后你要加一个手机号字段只需要复制一个校验函数然后把它挂进汇总函数完全不用动其他代码。4.2 实时反馈机制blur、input与防抖实时校验听起来简单但交互细节上有讲究。你是每次按键都校验还是等用户离开输入框再校验全键实时反馈对用户很不友好——我刚输入一个字母就告诉我用户名太短这种体验像催命一样。但等你点了提交按钮才提示错误又失去了实时性。所以我的建议是混用首次触发blur失去焦点后再开始实时校验。也就是说用户在输入过程中不清净打扰一旦他离开某个字段那个字段的错误提示就出现并且从此以后每次input都重新校验——这样一来用户在输密码时名字字段的错误会实时消失体验自然很多。代码上可以用一个dirty标志位实现input.addEventListener(blur, function () { this.dataset.dirty true; checkField(this); }); input.addEventListener(input, function () { if (this.dataset.dirty) { checkField(this); } });第一次失焦时开始校验后续输入实时联动。这个模式在真实项目里非常常见称之为dirty field脏字段模式。练过这个题之后你会对表单交互的常见套路有很直观的感受。再说防抖。有人会在input事件里做即时校验并同时把结果写进DOM如果校验逻辑复杂一点比如后续要带Ajax请求判断用户名是否被注册每次按键都发请求会把后端打爆。防抖的思路是用户停止输入300毫秒之后才执行真正的检查。实现防抖常写的代码是let timer null; input.addEventListener(input, function () { clearTimeout(timer); timer setTimeout(function () { checkUsernameUnique(this.value); }, 300); });每次输入先把上一个定时器清掉再挂一个新定时器保证只有用户停顿下来才会触发。这里有两个容易错的点一是timer变量必须定义在事件回调外面否则每次回调都重新赋一个局部变量清掉的是上一次的定时器逻辑会乱二是要注意this指向变化在setTimeout回调里this已经不是input了所以要么用箭头函数要么提前把this存到一个变量里。4.3 提交拦截与模拟请求这个题最后一步是处理表单提交。默认情况下form的提交事件会触发现实中的页面跳转刷新我们要拦下来。form.addEventListener(submit, function (event) { event.preventDefault(); const result validateForm(); if (result.isValid) { console.log(collectFormData()); showSuccessMessage(); } });event.preventDefault()是表单开发最重要的一个方法它阻止浏览器默认行为。这个知识点表现形式很基础但要理解它的用途得建立浏览器有默认行为的认知。表单提交刷新页面、点击链接跳转、右键弹出菜单这些都是浏览器默认行为JavaScript的很多工作其实就是拦截默认行为然后替换成自己写的行为。提交按钮的禁用逻辑我也要求了只要表单有任意一个字段未通过校验按钮就处于disabled状态。很多人会漏掉这个或者说只在提交时判断一次。但实际上线项目里按钮可用/不可用是一个非常直观的交互反馈用户看到按钮灰着就知道还有信息没填对。代码逻辑不复杂updateButtonState(); function updateButtonState() { submitBtn.disabled !validateForm().isValid; }只要有一个字段触发了input或blur校验就重新跑一次全表单校验并更新按钮状态。这个模式非常实用基本上所有长表单页面都值得这么做。但注意性能——每次都全量校验如果字段很多DOM操作频繁体感会有卡顿。这时候可以用一个简单的技巧校验函数只读字段值不操作DOM等校验结果拿到了再统一操作DOM更新错误提示。把读取和渲染分离是让程序结构变清晰的一大步。4.4 无障碍与细节加分项如果这个题想拿高分我还会看几个加分细节。第一个是错误提示文字要用aria-livepolite属性包裹这样读屏软件会在错误出现时朗读出来这是无障碍的基本功。第二个是输入框的边框颜色变化——无错误时是常规灰色边框错误时变成红色并且绑定一个红色阴影这个视觉效果能帮正常用户更快定位问题。第三个是按下提交时如果有错误焦点跳到第一个错误字段减少用户查找成本。这些小细节不是题目硬性要求但对培养良好的WEB开发习惯非常有帮助。我自己平时写表单必带aria-live因为成本极低、收益可观而且很多同事都容易忽略。关于正则表达式本身我多说一句不要试图写出一个完美的邮箱正则。网上流传的复杂正则可以匹配几乎所有符合RFC规范的真实邮箱地址但它们又长又难维护。实战中一个简单的/^\S\S\.\S$/完全够用它能挡住绝大多数明显无效的输入。别把精力浪费在正则的精巧上重点应该是把校验不通过时的提示文案写得清晰友好。5. 从菜鸟到及格再到优秀评分视角与建议5.1 三个层次的标准批改了三轮这类作业之后我基本能在一眼之内判断出一份作业处于哪个水平。这里把评分视角公开给大家方便你自己对照检查到了哪个层次。不及格水平说实话不多但存在页面在某个宽度下崩了待办事项的删除功能在刷新后失效表单点了提交之后页面跳转、原内容清空。这类问题都是基础原理没有理解代码属于教程给定型换一个场景就不会迁移了。这时候我的建议很简单别急着做更炫酷的东西先把三道题的要求逐条读一遍把每一个验收标准当成一个独立的检查项逐个解决。及格水平所有要求的功能都能跑通代码能正常工作但实现方式比较老实——比如校验逻辑全写在一个函数里事件绑定是逐个元素来的CSS用了不少position强制对齐。这个阶段的人已经掌握了基本语法和API差的只是设计意识和工程习惯。建议下一步是看一些高质量的代码示例对比别人的写法重点看别人如何拆分函数、如何组织数据结构。优秀水平功能完整代码结构清晰更重要的是能解释清楚为什么这么写。比如能说出为什么用事件委托而不是逐个绑定为什么用Grid而不是Flexbox为什么校验函数要返回错误字符串而不是直接操作DOM。以上三条如果能对答如流说明你不仅会写还理解了背后取舍。到这个程度你可以放心的去学框架了React或者Vue在你眼里会变成原来如此而不是这都什么魔法。5.2 做完之后还能怎么扩展这三道题做完相当于把WEB前端的读、写、交互三个基本功摸了一遍。如果你还有余力我非常推荐做三个方向的扩展每一个都直接把你的能力往上推一档把练习题二的数据结构升级给每条待办加createdAt字段按创建时间排序再加一个全部/未完成/已完成的筛选Tab。这一个扩展同时覆盖了排序、分类、状态管理三个知识点难度立刻上一个台阶。把练习题三的校验改成异步接口校验模拟一个用户名查重接口用setTimeout模拟延迟返回这就要用到上面的防抖逻辑了。做完之后你会直观理解什么是前后端交互为后面学Fetch、Axios打基础。给练习题一增加深色模式用CSS的prefers-color-scheme媒体特性自动切换配色变量用CSS自定义属性--main-bg管理。这个扩展可以让你提前接触设计系统和主题方案的概念是很多人工作两三年才接触的东西。我自己的体会是练习题做得再多如果只是照着示例敲一遍收获非常有限。真正有效的方式是做完一遍之后再自己给自己设几个扩展需求不要看答案硬着头皮实现。卡住了就翻文档翻完再卡这个过程才是技术成长最快的阶段。你会在处理一个又一个为什么没生效的问题时逐渐形成属于自己的排错思路这比背一百个API有用得多。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/15 18:48:08
Blob检测原理与工业级实践:从图像斑点定位到产线落地
2026/10/15 18:43:07
认知语言学入门:意象图式与隐喻系统如何塑造思维
2026/10/15 18:43:07
FPGA软核实战:在DE2上用OC8051实现点灯与中断验证
2026/10/15 22:34:38
OpenFlight供电方案全解析:树莓派27W电源与Geekworm电池HAT两条路线怎么选
2026/10/15 22:34:38
Linux离线安装GitLab RPM包:运行时框架配置指南
2026/10/15 22:34:38
30-seconds-of-python 字典技巧:用 `key_of_max` 一行代码找出字典中最大值对应的键
2026/10/15 22:34:38
三线交汇:构图控制、局部重绘与个性化定制的完整工作流
2026/10/15 22:34:38
Composition、Inpainting与Customization三线交汇:可控图像生成管线实战
2026/10/15 22:29:37
离合器与活塞连杆组仿真分析:从边界条件到热-结构耦合实战
2026/10/15 0:02:59
AI Coding时代的柔性之美:从静态软件到自演化软件,软件研发正在重新进化
2026/10/15 0:02:59
亨得利客户服务中心官方网点分布一览:2026年全国多城门店地址与最新联系电话公布
2026/10/15 0:02:59
防关联浏览器哪个好?三款产品实测差异解析
2026/10/13 12:25:09
流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南
2026/10/14 18:35:19
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别
2026/10/14 14:52:59
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容
2026/10/15 1:07:55
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/15 5:07:59
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/15 8:10:01
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)