一、练习3到底该练什么,先想明白再动手
"web前端练习3"这个标题看着简单,但它其实卡在一个很微妙的阶段。如果你是按顺序一路做过来的,练习1大概率是照着视频或教程敲一个静态页面,练习2可能是自己独立完成了一个带基础样式和简单交互的落地页。到了练习3,再重复"写个div、上个色、加个hover"这种操作已经没有太多增量了,这个阶段的练习重点应该转向三件事:
- 学会"按需求拆页面",而不是拿到设计稿就从头写到尾;
- 学会"让页面数据动起来",也就是通过接口或本地数据文件去渲染内容;
- 学会"遇到报错能自己排查",尤其是网络相关的问题。
我在带新人时经常讲一句话:前两个练习是在练手,第三个练习是在练脑子。这个阶段做得好的话,你会突然发现自己能看懂很多以前觉得高深的东西,比如框架为什么会存在、接口联调是什么意思、UI给了标注图之后开发要怎么还原。
所以这次练习,我给自己的设定是做一个"作品集风格的产品展示页"。页面本身涵盖了导航栏、轮播主视觉、卡片列表、表单区等常见的业务模块,同时加入了一个非常关键的动作——通过fetch请求一个本地的JSON数据文件来渲染卡片列表。这个动作直接决定了这次练习的量级:它不是纯静态页面了,而是一个"靠近真实业务"的页面。
这个练习适合谁?不管你是在校学生、转行自学的人,还是已经在做切图但还没碰过数据请求的初级开发者,都可以拿这个题目练一遍。它不需要你有任何框架基础,不需要懂Node.js,也不需要会构建工具,只要你有HTML、CSS、JavaScript的入门知识,再补上一点HTTP和JSON的概念就能跟完全程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
二、练习前的基础准备与整体思路
2.1 你需要安装和准备的工具清单
先把我这次练习实际用到的工具列一份清单。不是广告,都是免费或者社区常用的东西,你大概率已经装了大部分:
| 工具 | 用途 | 是否必须 | 备注 |
|---|---|---|---|
| VS Code | 代码编辑器 | 推荐 | 也可以用任意编辑器,但VS Code的Live Server插件非常好用 |
| Live Server插件 | 启动本地开发服务器 | 强烈推荐 | 后面会解释为什么不能直接双击打开HTML文件 |
| Chrome浏览器 | 调试页面 | 必须 | DevTools是排错的核心工具 |
| Git | 版本管理 | 推荐 | 练习3开始建议养成每次改动提交一次的习惯 |
| JSON格式化工具 | 校验数据文件 | 可选 | 出错时排查速度快很多 |
有一点我想单独说一下:很多新手会在这一步卡住,觉得"写个页面为什么要装一堆东西"。真正的理由是,做练习1、练习2的时候,页面里没有数据请求,浏览器双击打开HTML文件就能工作。但练习3一旦引入fetch请求本地JSON文件,浏览器基于安全策略会限制"file://协议"下的访问,这时候不启动本地服务器,页面大概率是白屏或报错。装Live Server这个动作,本质上是在提前模拟真实开发环境。
2.2 技术选型解析:原生三件套还是引入框架
这次练习,我没有用Vue或React,只用HTML + CSS + JavaScript原生实现。原因不是框架不好,而是练习阶段的目标决定了选型:
- 如果你还没完全理解DOM操作、事件绑定和异步请求,直接用框架很容易变成"会调用但不懂原理";
- 原生JavaScript写一遍网络请求、循环渲染、事件委托,你才会真正理解那些框架帮我们做了什么;
- 练习3的数据量不大,交互也不复杂,原生写法并不会让代码变得很难维护。
当然,如果你已经在简历上写了熟悉Vue,那练习3可以考虑一个小任务:先原生实现,再用框架实现一遍,对比两者的差异。但如果你是第一次接触数据请求,我很不建议在练习3就跳进框架,先把这一步走扎实,后面学框架会顺畅很多。
2.3 页面架构先画出来,再动手写代码
我习惯在写任何代码之前,先在纸上或者用简单的文本工具把页面结构列出来。这次练习的产品展示页,我把它划分成了5个区块:
- Header导航区:包含Logo、导航菜单和一个按钮,滚动到顶部时固定住;
- Hero主视觉区:一个大面积的首屏,左侧文字、右侧插画或者图片,保证第一次打开页面的人能在一秒内看懂这个站是干什么的;
- 作品展示区:这是本次练习的核心,数据从data.json中读取,用JavaScript循环渲染成卡片;
- 功能特性区:展示产品的几个核心卖点,用小图标加标题加描述的结构;
- 表单区块:一个联系或订阅表单,提交时先做前端校验,再弹出一个结果提示。
这个结构没有任何花哨,是业务开发里最常见的经典布局。但它带来的训练价值很明确:页面上有的东西,一部分是写死的,一部分是需要数据驱动的,你得清楚每一块内容应该用什么方式去实现。另外,在写CSS之前,我建议先给自己的页面选定两到三个主色、一个强调色、一个正文字号和一个标题字号,避免写到一半开始纠结颜色和大小。
三、动手搭建:从页面骨架到样式还原
3.1 HTML结构规划与语义化
第一步是搭HTML骨架。你可能觉得结构很简单,实际上这里有个容易忽略的考点:语义化标签的使用。不要整篇都是div,我这次使用了header、nav、main、section、article、footer这些标签。这么做的直接理由是SEO友好,间接理由是当你工作以后拿到别人写的代码,语义清晰的页面会减少很多沟通成本。
我把作品展示区的结构预先定成这样:
html复制<section id="works">
<div class="container">
<h2>精选项目</h2>
<p class="subtitle">以下数据来自本地 data.json</p>
<div class="works-grid" id="worksGrid">
<!-- 这里将由 JavaScript 动态生成 -->
</div>
</div>
</section>
注意这个works-grid是空的,它专门留给JavaScript去填充。这是练习3和前面纯静态页面的一个明显分水岭:你的HTML不再是"内容全部写死",而是只写骨架,内容靠数据驱动。养成这个习惯以后,你对接任何前后端协作的项目都会很自然。
3.2 CSS布局方案:为什么用Grid而不是Flex
这次练习的卡片列表,我用的是CSS Grid布局。很多新手会问Grid和Flex的区别,我用大白话解释:Grid是二维布局,适合规划"一行几列,以及跨行跨列"的页面区域;Flex是一维布局,适合处理"一排内容在主轴上的排列方式"。这次的作品卡片是多行多列的网格,所以Grid更合适。
需要注意响应式断点的设置。我的做法是优先写移动端,再渐进增强。换句话说,先定一个默认的单列布局,然后用min-width媒体查询去适配更大屏幕:
css复制.works-grid {
display: grid;
grid-template-columns: 1fr;
gap: 24px;
}
@media (min-width: 600px) {
.works-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 992px) {
.works-grid {
grid-template-columns: repeat(3, 1fr);
}
}
这些做法背后都是有意图的:移动端默认单列,保证内容可读性;平板两列,桌面三列。gap给间距,避免再额外写margin去调整每个卡片之间的距离。CSS的很多门道都是在这些不起眼的小细节里,能把布局思路表达清楚,跟那些只会堆class的人是能一眼区分开来的。
3.3 字体、间距与视觉节奏的取舍
对于练手项目,视觉上最容易翻车的是字体大小和间距完全没有体系。我的做法是定义一组CSS变量:
css复制:root {
--primary-color: #2563eb;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--radius-md: 12px;
--space-sm: 12px;
--space-md: 24px;
--space-lg: 48px;
}
这套变量的好处是,后面如果觉得主色不对,只要改一个地方,全站颜色都跟着变,不需要满篇去搜颜色值。间距也一样,统一使用四个档位,避免那种"这里16px、那里15px"的杂乱感。你去看那些做得好的产品站,视觉节奏感往往不是设计多华丽,而是重复和统一做得足够好。
3.4 导航栏吸顶效果和滚动阴影的小细节
导航栏我觉得值得单独写一下,因为这里面涉及一个高频场景和一个经典的细节优化。高频场景是"滚动后固定导航栏",我用的方案是position: sticky:
css复制.site-header {
position: sticky;
top: 0;
z-index: 100;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
}
sticky和fixed最大的区别在于,sticky元素仍然占据文档流空间,不会导致下面的内容突然顶上去,体验上更平滑。这里的backdrop-filter是让导航栏有毛玻璃效果,显得更精致。唯一要注意的是浏览器兼容性,backdrop-filter在现代浏览器基本没问题,但如果你需要兼容很老的浏览器就得加@supports来判断。
第二个细节是"滚动后给导航栏加阴影"。页面刚加载时,导航栏紧挨着顶部的内容,不需要阴影;但一旦往下滚,导航栏和内容之间有明显分层才好看。我加了一个非常简单的滚动监听:
javascript复制const header = document.querySelector('.site-header');
window.addEventListener('scroll', () => {
header.classList.toggle('scrolled', window.scrollY > 10);
});
CSS里配合:
css复制.site-header.scrolled {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
这个效果很小,但经常在大厂官网里看到,它可以作为面试时聊"你如何优化体验细节"的一个案例。
四、核心环节:动态渲染数据与network unavailable报错排查
4.1 准备数据文件data.json
在作品展示区不写死HTML之后,我们需要一个数据源。我在项目根目录建了一个data文件夹,里面放了一个data.json。结构很简单,就是数组,里面每个对象代表一张作品卡片的信息:
json复制[
{
"id": 1,
"title": "品牌官网设计",
"category": "网页设计",
"image": "images/work-1.jpg",
"description": "一个完整的品牌形象展示官网,包含首页、产品页、博客等模块。",
"link": "#"
},
{
"id": 2,
"title": "数据可视化后台",
"category": "后台系统",
"image": "images/work-2.jpg",
"description": "为企业内部打造的销售数据看板,支持多维度筛选与图表导出。",
"link": "#"
}
]
这里我给每张卡片准备了ID、标题、分类、图片、描述和跳转链接。这些字段在后面渲染时会一一对应到卡片的DOM结构上。JSON里要注意的是,最后一个对象后面不能再有逗号,而且所有的键和字符串值都必须使用英文双引号,否则浏览器解析会直接报错。
4.2 使用fetch请求本地JSON并渲染
接下来是练习3的重头戏。我写了一个独立的render.js文件,里面用一个init函数来拉取数据并渲染:
javascript复制async function loadWorks() {
const grid = document.getElementById('worksGrid');
try {
const response = await fetch('./data/data.json');
if (!response.ok) {
throw new Error('请求失败,状态码:' + response.status);
}
const works = await response.json();
grid.innerHTML = works.map(item => {
return `
<article class="work-card">
<div class="work-card__image">
<img src="${item.image}" alt="${item.title}" loading="lazy">
</div>
<div class="work-card__body">
<span class="work-card__category">${item.category}</span>
<h3>${item.title}</h3>
<p>${item.description}</p>
<a href="${item.link}" class="work-card__link">查看详情</a>
</div>
</article>
`;
}).join('');
} catch (err) {
grid.innerHTML = '<p class="error-tip">内容加载失败,请稍后重试</p>';
console.error('加载数据出错:', err);
}
}
loadWorks();
这里有几个点值得展开说。第一个是fetch请求返回的response对象,即使请求返回404或者500,也不会直接走catch分支,所以必须在代码里对response.ok做判断。第二个是模板字符串拼接HTML,初学者容易在反引号或${}上写错,报错时优先级最高的排查手段是打开浏览器控制台看具体错误信息。第三个是catch里我做了降级处理,把提示信息渲染到页面上,而不是让用户看到一块白屏,这是真实项目里的基本素养。
4.3 本地页面报错network unavailable的完整排查过程
现在来说说热搜词里那个高频问题:web前端项目运行显示network unavailable怎么解决。这个报错我在教新人的时候见得太多了,它的出现场景几乎一模一样——写完练习代码,双击HTML文件,点击按钮或页面加载时触发网络请求,然后控制台就报了network unavailable或者Failed to fetch。
原因概括起来就是:
- 页面是通过file://协议打开的;
- JavaScript尝试使用fetch去请求本地文件;
- 浏览器出于安全策略,禁止页面在file://环境下发起这类请求,所以连接层面看起来就是"网络不可用"。
这个报错具有相当的迷惑性,因为你的网络明明是通的,浏览器却告诉你network unavailable,很多人会去关闭防火墙、重启路由器,其实方向完全错了。
解决方法是启动一个本地开发服务器,用http://协议来访问页面。最省事的做法是我前面提到的VS Code Live Server插件。装好后右键HTML文件,选择"Open with Live Server",它会自动在浏览器里打开一个类似 http://127.0.0.1:5500/index.html 的地址。到这一步,再刷新页面,fetch请求就能正常工作了。
我建议你在控制台的Network面板里观察一下请求状态:正常情况下,你会看到data.json的状态码是200,类型是fetch/xhr。如果状态码变成了红色404,就说明文件路径写错了,这时候要看Relative Path是不是正确。
4.4 排查network unavailable时的三张底牌
如果你已经用Live Server打开还是报network unavailable,不要慌,按这个顺序排查:
- 确认浏览器地址栏前缀是http://localhost或http://127.0.0.1,只要还是浏览器地址栏显示file://C:/...这种,说明你是直接双击打开的,没有经过服务器;
- 打开DevTools的Console面板,看完整报错内容,如果是Failed to fetch,多半是路径不对或者服务没启动成功,如果报错是CORS policy,说明浏览器跨域策略拦截了,需要换一个开发服务器或者在服务端配置允许跨域;
- 确认fetch的URL路径。相对路径是从当前页面的目录出发去查找的,我在项目里把data.json放在data文件夹下,页面文件在根目录,所以正确写法是fetch('./data/data.json'),如果少了一个斜杠或者写成data.json就会404。
这三张底牌覆盖了我见过的绝大多数情况。多说一句,这类问题一定要自己动手踩一遍坑,因为"network unavailable"这个报错在真实的联调环境也会遇到,比如后端接口还没启动、代理配错了、证书过期了,都可能以类似形式出现。练习阶段把排查思路练熟,后面工作里会省很多事。
4.5 fetch和axios该怎么选
写完fetch之后,估计会有人问:为什么不用axios?axios看起来更常用啊。我的看法是:学习阶段一定先用原生fetch把HTTP请求的机制搞明白,再引入axios去体验更便捷的API。axios的核心优势在于可以统一配置拦截器、设置超时、更方便地处理错误码,但它本质上还是对XMLHttpRequest或fetch的封装。
在练习3这次场景中,我们只需要请求一个JSON文件,没有请求拦截、没有超时控制、没有取消请求的需求,用fetch完全就够了。但如果你希望简历上的项目看起来更接近业务,也可以尝试在练习中引入axios,通过CDN方式:
html复制<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
然后请求就变成了:
javascript复制axios.get('./data/data.json')
.then(res => {
const works = res.data;
// 渲染逻辑...
})
.catch(err => {
console.error(err);
});
不过这里有一个训练顺序的建议:fetch遇到报错时,你需要理解HTTP状态码和Promise的机制;axios遇到报错时,同样需要这些基础。先练fetch等于先把底层走了一遍,之后学axios基本就是看一下文档就能上手。
五、进阶练习方向与面试高频问题对照
5.1 这次练习能延伸出的4个进阶改动
练习3做完以后,不要急着立刻做练习4。我建议你先在这个页面上做几次小改动,每个改动都能逼你学到一个新知识点。
第一个改动是给作品数据增加一个分类筛选功能,页面上放几个按钮,点击"网页设计"就只显示对应分类。这个功能会倒逼你理解什么是数据状态、如何重新渲染视图。实现思路很简单,在内存中维护一个当前选中的分类值,点击按钮时修改这个值,然后基于这个值去过滤works数组并重新调用渲染函数。这其实就是前端框架里"数据驱动视图"的雏形。
第二个改动是给卡片加一个"加载更多"按钮,每次点击从数据文件中多加载几条。这时你可以模拟延迟,用setTimeout包一层,让进度条或loading动画有存在的意义。这个练习的价值在于提前体验真实项目中的loading状态管理。
第三个改动是给表单提交做一个假异步操作。比如用户填写表单后,点击提交按钮,按钮进入"提交中"禁用状态,等一秒钟后显示"提交成功"。这种交互在真实项目里极为常见,可以让你学会处理用户重复点击的问题。
第四个改动是把图片路径替换成真实的远程图片链接,看看加载速度和布局偏移怎么处理。这里面会引出一个非常重要的属性loading="lazy",还有CSS里的aspect-ratio。占位图导致页面跳动的问题,如果你在练习中就注意到,后面在工作中会很加分。
这些改动没有一个需要新工具,都是在当前项目上进一步深化JavaScript的掌握。做完任何一个,你的项目都能在面试时讲出更多的"为什么"。
5.2 前端基础面试题:练习3能回答哪些问题
练习3做下来,其实会自然覆盖不少web前端面试常见题。我把相关的题目和这次练习中的对应回答整理成一个表,方便你做知识复盘:
| 面试题 | 在这次练习里你能怎么回答 |
|---|---|
| 讲讲你对语义化HTML的理解 | 页面中使用了header、nav、section、article、footer等标签,解释它们对SEO和无障碍访问的意义 |
| CSS Grid和Flexbox的区别是什么 | Grid用于二维整体布局,Flexbox用于一维排列,举例说明作品展示区为何用Grid |
| 什么是事件委托 | 如果你给作品卡片动态生成的按钮绑定事件,不应该逐个绑定,而应委托给父容器,通过e.target判断实际点击目标 |
| 闭包是什么,项目中哪里用到 | 在防抖节流功能里,闭包保存定时器ID;也常见于循环绑定事件的场景 |
| 异步请求中加loading状态怎么做 | 在请求前显示loading,请求完成后移除,catch中也要处理,保证任何分支页面都有反馈 |
| 什么是跨域问题,如何解决 | fetch一个不同源接口时被浏览器拦截,可以用开发代理、CORS头等方式处理 |
| Cookie和localStorage有什么区别 | 存储位置、大小限制、请求携带、生命周期等维度对比 |
这张表里的第一个问题甚至可以作为面试的开门题:你在项目里部署了什么,为什么这样部署?如果你只是照着视频敲了一遍页面,很可能支支吾吾答不上来;但如果你从项目结构、数据文件处理、fetch报错排查、响应式断点选择这些角度去讲,面试官会感觉到你是自己动手想过的。
5.3 UI设计和Web前端开发怎么选,到底哪个好学
既然热搜里有"ui和web前端开发哪个好学"这个问题,这里就顺带聊几句,毕竟练习3做完的人往往会开始纠结接下来的职业方向。
UI设计和Web前端开发是两条有交集但不完全相同的路。UI更偏向视觉表达,需要审美、构图、色彩、交互原型能力,日常打交道的是Sketch、Figma、Photoshop,核心产出是设计稿和规范。Web前端更偏向代码实现,需要写HTML/CSS/JavaScript,理解网络请求和浏览器渲染机制,日常打交道的是编辑器、调试工具和接口文档。
"哪个好学"这个问题其实问错了方向。两者都不轻松。UI需要持续提升审美和说服力,你的设计稿要能经得起评审和开发"能不能实现"的挑战;前端需要持续跟上语言和工具链的更新,ECMAScript新特性、构建工具、框架版本都在变。我见过UI转前端的人,也见过前端转UI的人,做得好的都有一个共同点——他们都是对作品有要求的人,而不是为了逃避某类困难才转了方向。
如果非要从入门门槛来判断,前端入门时的正反馈来得更快:你写几行代码,浏览器里立刻就能看到结果。UI却要花很长时间在布局、色彩、字体的细腻调整上。但入门快不代表天花板低,前端越往深走越庞杂。所以这个问题建议这样思考:你更喜欢把一个页面从视觉上打磨出美感,还是更喜欢把一个功能从逻辑上跑通?答案是什么,方向就选什么。
六、练习过程中的常见报错与避坑记录
这个章节我在做练习时也踩了不少坑,整理成一份速查表,希望你在做的时候能少走弯路:
| 报错或现象 | 原因 | 处理办法 |
|---|---|---|
| 双击HTML文件,fetch请求报network unavailable | file://协议下浏览器不允许fetch本地文件 | 使用Live Server或任意本地静态服务器访问页面 |
| data.json请求404 | 相对路径写错,或文件名拼写错误 | 检查fetch的URL路径,确保data文件夹和文件位置正确 |
| 页面显示但卡片区空白 | JavaScript运行报错,大概率是JSON解析失败或选择器错误 | 打开DevTools控制台看具体错误;用JSON格式化工具校验data.json |
| JSON解析报错Unexpected token | JSON格式不合法,比如多了逗号、用了单引号 | 使用JSON校验工具,复制内容进去会直接提示第几行出错 |
| 滚动导航栏样式不生效 | classList.toggle写反,或CSS选择器写错 | 在DevTools的Elements面板检查class是否成功添加 |
| 图片不显示 | 图片路径错误或文件不存在 | 确认images目录下的文件名和大小写;推荐使用相对路径 |
我特别想说一下排查问题的思路:永远先分前端还是数据、再分语法还是逻辑、最后看网络还是渲染。很多新人一上来就乱猜,改来改去浪费时间。正确做法是打开DevTools,先把Console的红色报错读明白,再结合Network面板看请求有没有发出去、状态码是什么。绝大多数练习项目的报错都能在两分钟内定位,前提是你不要慌,一处处看。把"读报错"当成一种能力来练,这是前端开发入门的核心竞争力之一。
另外补一个关于代码规范的小经验:如果你打算把这个练习项目当作作品放进简历,建议在项目根目录补一个README.md,写清楚项目运行方式——"npm install"或者"使用Live Server打开index.html"。很多面试官会真的去跑一下你的项目,如果看README五分钟跑不起来,印象分会大打折扣。
七、前端练习的提效习惯:Git提交与代码注释
练习3开始,我对自己的一个强制要求是:每完成一个模块就提交一次代码。不要等到所有功能做完了再一次性commit,那样万一改崩了没法回退。常规的任务粒度提交是这样的:
bash复制git init
git add .
git commit -m "feat: 初始化项目结构,完成头部导航和主视觉区域"
git add .
git commit -m "feat: 完成作品展示区fetch渲染,新增data.json数据文件"
git add .
git commit -m "feat: 添加响应式布局与表单校验交互"
这个过程对新人来说可能有点繁琐,但好处非常大。第一,你在练习中做的每一步都有记录,出问题可以随时回溯;第二,你提前养成了工作中一定会用到的协作习惯;第三,一个commit历史干净的项目,本身就是你态度的一种证明。
注释方面我的观点是"命名清晰优先,注释补充意图"。如果一个变量叫data,那写再多注释也救不了;如果变量叫worksFromJson,代码基本一读就懂。只有在逻辑比较复杂、或者有特殊业务背景的地方才需要写注释,解释"为什么这么做"而不是"这段代码做了什么"。
八、个人总结与经验心得
这次练习做下来,我最强烈的一个体会是:把练习从"照着做出来"升级到"主动设计并解决问题",成长速度会完全不一样。我在带新人的时候发现,同样是一套教学视频,有人学完就会写页面,有人学完还是只会改改字和颜色。区别不是天赋,而是是否愿意主动给自己出题、主动去踩坑、主动去查资料解决。
"web前端练习3"这个题目本身没有标准答案,但完成它之后,你应该能回答几个更高级的问题:你为什么用Grid而不用Flex?你如何处理fetch请求的异常?本地文件请求报network unavailable时你的排查思路是什么?这些问题的答案,比页面本身的视觉效果更有面试价值。
最后再分享一个小技巧:练习完成后,隔两周再打开项目,试着在不看代码的情况下重新实现一遍。你会发现第一次写时没理解透的地方全都会暴露出来。前端就是这样一门手艺,知道和做到之间,隔着一次又一次的刻意练习。
