有一类作业,看上去只是“做一个页面”,其实它是整个前端学习里最重要的一次分水岭。我第一次带新人或者辅导学生时,都会特别强调:web前端第一次作业能不能做好,不在于代码写了多少,而在于你有没有把“写代码”和“做页面”这两件事分开。很多同学第一次拿到题目就直接开写,结果不是样式乱掉,就是脚本不生效,折腾一晚上,最后连问题出在哪都不知道。这篇文章我就拿一份非常典型的第一次作业——做一个带交互的个人书单分享页——来完整拆一遍:从读题、搭结构、写样式、加交互,到本地运行报错怎么排查,全部过一遍。整个过程不涉及框架、不依赖后端,只用 HTML、CSS、原生 JavaScript,适合所有刚接触前端的人照着做,也给那些已经写完但跑不起来、改不动、不知道下一步怎么办的同学一份直接的参考。
1. 作业全貌:交作业前先把需求盘明白
很多人把作业写砸,不是技术不行,是根本没看懂题目。第一次作业往往只有一句话,比如“请用 HTML+CSS+JavaScript 制作一个个人主页”,这句话的信息量其实非常大。它默认你要掌握三块东西:结构怎么组织、样式怎么表现、交互怎么触发,缺一不可。实际工作中前端岗位也基本沿着这个划分走,所以这三次作业其实就是职业能力的预习。
1.1 需求背后藏的基础功清单
你拿到题目以后,别急着建文件,先试着把需求翻译成具体的功能点。以个人书单分享页为例,我会先列出下面这份清单:
- 页面上至少要有几个内容区块,例如头部简介区、书单列表区、底部信息区;
- 书单里的每本书需要展示书名、作者、一句推荐理由,这考察你对 HTML 标签嵌套的理解;
- 页面需要整体可看,不能所有内容挤在一起,这考察 CSS 盒模型和布局能力;
- 页面至少要有一个交互:比如点击“展开推荐语”按钮后,文字从隐藏变为显示,这考察 JavaScript 对元素状态的控制能力;
- 文件组织要规范,index.html、style.css、script.js 分开存放,这考察你对前端工程基本结构的认知。
我在实际辅导中见过太多作业,功能都实现了,但所有代码全写在同一个 html 文件里,CSS 直接内联,JS 塞在 body 末尾,浏览器一打开确实能看,可用性极差。作业阶段如果不养成分离文件、分离关注点的习惯,后期接触 vue、react 这类工程化框架时,会有很强的割裂感。
1.2 技术选型:基础三件套为什么足够
有同学会问,既然以后要学框架,第一次作业能不能直接用 Vue?我的建议是别,尤其是一门课的第一份作业。原因很简单,作业的目的是验证你对底层语言的理解,而不是验证你会不会装脚手架。原生 HTML、CSS、JavaScript 三者在浏览器中的执行逻辑非常直观,你能清楚地看到“我改了 CSS 里的颜色,页面就变色了”“我改了 JS 里的条件,按钮就不再响应了”。这种即时反馈对建立信心非常关键。
对于 JavaScript 部分,第一次作业用原生 DOM 操作就够了。后面项目中需要快速开发,可能会引入 jQuery,比如在 Java Web 项目里经常用 jQuery 配合后端写审批流、列表渲染之类的交互,那是另一个话题。但第一次作业先别碰库,把 document.querySelector、addEventListener 这些原生方法练熟,理解“事件—响应—状态更新”这条链路再去学 jQuery,你会发现 jQuery 就是给原生方法套了个更短的写法,根本不用费劲背。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零手写页面:三种核心技术一次说清
我自己写前端有个习惯,永远先写 HTML,再写 CSS,最后写 JS。这个顺序不是随便定的,而是在无数次思维混乱中总结出来的。HTML 决定内容层,CSS 决定表现层,JavaScript 决定行为层,三者本来就是递进关系。先想清楚页面上放什么,再去想怎么摆好看,最后才去想怎么点着好玩。
2.1 HTML 骨架与语义化:这决定页面被怎么理解
打开编辑器,先建一个 index.html,把基础骨架写出来。这里有一个细节:DOCTYPE 声明一定要写上,而且必须写在第一行。有些同学删掉它以后页面也能显示,但浏览器会进入“怪异模式”,盒模型的计算方式会和标准模式不同,css 里随便一个 width: 100% 都可能不按预期渲染。
html复制<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的书单分享</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="page-header">
<h1>我的书单分享</h1>
<p>记录近期读过的几本书,也记录自己的思考。</p>
</header>
<main class="book-list">
<article class="book-card">
<h2>《活着》</h2>
<p class="book-author">余华</p>
<button class="toggle-btn" data-target="desc1">查看推荐语</button>
<p id="desc1" class="book-desc hidden">
这是一本让人重新思考“生命”这个词的小说,文字干净却有力量。
</p>
</article>
<article class="book-card">
<h2>《小王子》</h2>
<p class="book-author">圣埃克苏佩里</p>
<button class="toggle-btn" data-target="desc2">查看推荐语</button>
<p id="desc2" class="book-desc hidden">
每个年龄读它都会读出不同的东西,适合放在床头反复翻。
</p>
</article>
</main>
<footer class="page-footer">
<p>前端学习第一天</p>
</footer>
<script src="script.js"></script>
</body>
</html>
注意我在 HTML 里用了 header、main、article、footer 这些语义化标签。它们和 div 在视觉上没有区别,但对阅读页面结构的开发者来说是极大的帮助,搜索引擎和辅助技术也能更好地理解页面层级。第一次作业就开始坚持语义化,写久了以后,你的代码会天然地比同龄人“高级”一个档次。
2.2 CSS 布局实战:从浮动到 flex 的避坑思路
CSS 是第一次作业里最容易崩溃的环节。就好比盖房子时水泥没调好,整体自然而然就垮了。我的建议是先通篇设一个 box-sizing: border-box,这能避免你在算宽度时被 padding 和 border 干扰。为什么?因为默认的 content-box 模式下,一个宽度为 200px、内边距为 20px 的盒子实际占用 240px;而 border-box 保证你设的 200px 就是最终宽度。第一次作业阶段,这个区别最容易在左右两栏布局里让你怀疑人生。
css复制* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
background-color: #f7f7f7;
color: #333;
line-height: 1.6;
}
.page-header {
text-align: center;
padding: 40px 20px;
background-color: #fff;
border-bottom: 1px solid #eee;
}
.book-list {
max-width: 720px;
margin: 30px auto;
padding: 0 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.book-card {
background-color: #fff;
border-radius: 10px;
padding: 24px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.book-author {
color: #888;
font-size: 14px;
margin: 6px 0 12px;
}
.toggle-btn {
border: none;
background-color: #4a7cf7;
color: #fff;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
}
.book-desc.hidden {
display: none;
}
.book-desc {
margin-top: 14px;
padding: 12px;
background-color: #f0f4ff;
border-radius: 6px;
}
这套样式里有几个值得记下来的点:max-width 配合 auto 外边距,可以让内容在宽屏下居中,而不是铺满整个浏览器;flex 布局负责纵向排列卡片,并用 gap: 20px 来控制间距,比在每张卡片上单独写 margin-bottom 干净得多。对第一次作业来说,把 flex 的 justify-content、align-items、gap 三个属性练熟,基本就能应付绝大多数布局。
2.3 JavaScript 交互:别急着写功能,先把调试环境跑通
第一次写 JavaScript 的人最常做的事,是把代码放在 script.js 里,然后打开页面发现没反应,接着就开始怀疑自己是不是写错了。要避免这个问题,有一个前置动作:打开浏览器的开发者工具,再看 Console 面板里有没有红色报错。这就像学开车,先学会看仪表盘再上路。
写交互的流程应该是:先选中元素,再监听事件,最后修改状态。以我上面书单里的按钮为例:
javascript复制const buttons = document.querySelectorAll(".toggle-btn");
buttons.forEach(function (button) {
button.addEventListener("click", function () {
const targetId = button.getAttribute("data-target");
const desc = document.getElementById(targetId);
if (desc) {
desc.classList.toggle("hidden");
button.textContent = desc.classList.contains("hidden")
? "查看推荐语"
: "收起推荐语";
}
});
});
这里有个新手特别容易踩的坑:querySelectorAll 返回的不是数组,而是一个 NodeList。虽然它支持 forEach,但不支持 map 返回新数组,也不支持 find。如果你在工程中想筛选这些元素,先转换成数组再操作会更顺手。另一个坑是 getElementById 拿不到元素时返回 null,如果你不做判断直接调用 .classList,代码会直接在控制台报错,页面交互自然就停了。这就是我为什么在代码里加了 if (desc) 这个判断。
3. 实操手记:自己动手做完一份前端作业的全过程
理论说再多,不如把一份作业从头到尾跑一遍。我就在这里记录一次完整的实操过程,从建目录到本地运行,带你看看每一步都在干什么,以及中途会碰到哪些“无声的坑”。
3.1 目录结构与文件创建
打开终端,先建立一个干净的目录。别小看这一步,一个规范的项目目录结构,能让你的代码在后续扩展时不乱。
bash复制mkdir book-list
cd book-list
touch index.html style.css script.js
如果你在 Windows 上没有 touch 命令,直接在编辑器里手动新建这三个文件也是一样的。关键是最终你的目录长这样:
text复制book-list/
├── index.html
├── style.css
└── script.js
有些同学会把 CSS 和 JS 放进 css 和 js 子目录,这完全没问题,但需要同步修改 HTML 里的文件路径。假如你的 css 文件在 assets/css/style.css,那 link 标签里的 href 也要写成 assets/css/style.css。我第一次教别人写前端时,最常排的错就是文件结构建了子目录,路径却还是平级写法,白屏还找不到原因。
3.2 本地运行与第一个“网络不可用”
写完了文件,打开浏览器,结果遇到一个现象:页面确实正常显示了,但是代码编辑器里的某种网页预览运行时报错“network unavailable”,感觉项目没起来,这是第一次作业里特别常见的现象。
先说原因。纯前端页面要运行,有两条路:一是直接双击 HTML 文件,用 file:// 协议打开,这种方式通过本地浏览器能直接显示静态页面;二是启动一个本地服务,用 http://localhost:端口号 访问,如果你写了 JavaScript,而且它涉及请求本地的图片、JSON 数据或者做模块化加载,就必须走本地服务。
很多编辑器自带的预览功能默认帮你起了一个服务,但由于端口占用、代理策略或服务器还没来得及启动,页面里请求的资源就会短暂显示“network unavailable”。这时候首先不要怀疑代码写错了,先看两件事:
- 浏览器地址栏里到底是
file://开头还是http://localhost开头; - 右键点击页面,选择“检查”,打开 Network 面板,刷新页面,看哪个请求显示为红色失败。
如果之前的资源加载全部失败,通常是本地服务没有正常起来。此时把使用中的“预览”或者“Live Server”这类插件重启一次,一般能解决问题。如果重启还是不行,就换端口启动,或者把项目目录复制到一个路径中没有中文和空格的位置,因为某些服务器对中文和空格的处理不够友好,容易导致资源路径解析失败。
我自己最常推荐的方式是使用 VS Code 里的 Live Server 插件。安装后,在 HTML 文件上右键选择“Open with Live Server”,它会自动在 http://127.0.0.1:5500 起一个服务,浏览器会自动打开页面。这种方式有两个额外好处:一是你修改并保存代码以后,浏览器不用手动刷新,页面会自动更新;二是它天然模拟了线上服务器的运行环境,资源加载行为和后端部署时更一致。
3.3 样式和脚本联调的关键一步
当页面能正常打开,也看得到完整结构和样式以后,接下来要验证的是 JavaScript 交互。这个阶段需要形成“先看控制台、再看元素、最后看网络”的排查三步法。假如我点击按钮后推荐语没有展开,打开 Console 看是否有报错;如果没有报错,切到 Elements 面板,检查那段 <p> 的 class 属性是否发生了变化,变成了 book-desc 而不是 book-desc hidden;如果 class 变了但页面视觉没变,问题就出在 CSS 上,大概率是选择器写错,或者被别的规则覆盖。
举个例子,有同学把按钮放在 <p> 标签内部,HTML 解析器可能会自动修正这种嵌套关系,导致 DOM 树和你预期的不一样,事件绑定自然失败了。对于这种问题,最好的方法就是用浏览器 Elements 面板所见即所得地看一遍实际 DOM 结构。养成这个习惯,以后到了 Vue 或者 React 的开发环境,你也会自然地用虚拟 DOM 的结构来思考组件层级。
4. 第一次跑前端项目,最容易绊倒人的几类问题
我现在带人做 web 前端第一次作业,一定会在最后加一个“debug 环节”。因为前端学习本质上就是一次次的“预期违背”——你觉得样式应该这样,结果页面偏要那样;你觉得点击一定生效,结果控制台冷冰冰报一个错。这里的经验积累得越多,处理真实项目时越稳。
4.1 白屏问题的经典排查路线
白屏可以说是前端新手最常碰上的“翻车现场”,而且它往往不是单点问题。我的排查顺序如下:
- 打开 Chrome DevTools 的 Console 面板,看有没有红色的报错信息。如果有,点击报错右侧的文件链接,它会直接跳到出错的代码行;
- 如果 Console 没有报错,但页面仍然空白,右键点击页面选择“查看页面源代码”,确认 HTML 内容是否完整。如果 HTML 根本没有渲染,检查服务器是否正常启动;
- 如果 HTML 在页面源代码里能看到,但视觉上空白,把浏览器窗口宽度调小或调大,再用 Elements 面板检查各容器的高度是不是变成了 0。很多新手在给块级元素加浮动以后,忘了清除浮动,或者父容器没有设置高度,导致内容区塌陷成一条线。
有个真实的案例:一个同学做了一个顶部导航栏,把里面的 li 全部设成 float: left,父容器 ul 的高变成了 0,背景色也看不见,看起来就像整个导航栏消失了一样。解决办法是在父容器上使用 overflow: hidden,或者给父容器加上 display: flex。第一次作业阶段就去背“清除浮动”的八种方法意义不大,直接记住“优先用 flex 布局,可以规避掉绝大多数浮动塌陷问题”就够了。
4.2 图片加载失败与 CSS、JS 资源请求失败
页面结构出来了,可图片显示成一个碎图标,样式完全没加载,这绝大多数是路径问题。我会建议第一次作业一律用相对路径,也就是从当前 HTML 文件出发去寻找资源。比如:
text复制book-list/
├── index.html
├── style.css
└── images
└── cover.jpg
在 index.html 里访问这张图片,正确写法是 images/cover.jpg,不需要加斜杠开头。如果你写成了 /images/cover.jpg,浏览器会把它解析为你网站根目录下的资源。本地用 Live Server 打开时,根目录是项目根目录,这好像没问题,可一旦你把 index.html 直接拖进浏览器以 file:// 打开,那一瞬间 /images 会被解析成磁盘根目录,图片就找不到了。所以我给新手的建议是:除非明确知道自己在做什么,否则尽量别写以 / 开头的绝对路径。
4.3 控制台报错的常见类型速查
第一次作业的 JS 报错,最常见的无非几类:语法错误、类型错误、引用错误。我给它们建一个速查表,方便你对照排查。
| 报错关键词 | 含义 | 常见场景 | 解决思路 |
|---|---|---|---|
Unexpected token |
语法错误 | 少了括号、多了逗号、引号不配对 | 查看报错行号,从该行往前找 |
Cannot read properties of null |
试图读取空对象的属性 | querySelector 没找到元素,却直接操作它 |
给元素对象加上判空逻辑 |
xxx is not defined |
引用了不存在的变量或函数 | 单词拼错、作用域不对 | 检查变量声明位置,是否在函数内 |
Cannot set property of undefined |
给 undefined 的属性赋值 | 对数组的某个不存在下标直接赋值 | 先确认数据存在再继续操作 |
这些报错并不高深,应对它们的思路也一致:不要盯着控制台发呆,直接把报错信息复制到搜索引擎里搜索,基本都能找到解释。但真正高效的开发者会反过来思考报错背后的原因,比如 Cannot read properties of null,本质上是在提醒你“没有找到那个 DOM 节点”。那么你可以先看看脚本是不是放在 <head> 里、body 还没渲染就执行了,或者选择器是否写错。这种“从错误反推代码逻辑”的能力,会在日后面试和大项目中给你很大加分。
5. 作业和真实项目之间的差距在哪
写完一次作业,并不意味着就懂前端了。作业和真实项目之间隔着的核心不是技术栈,而是对“前端在整个业务系统中到底负责什么”的理解。很多将来工作的同学,可能会接触到老项目,比如 Java Web 里的 JSP 页面,前端代码会嵌在服务端模板中,你还得用 JavaScript 和 jQuery 去操作动态生成的 DOM,实现列表渲染、条件控制、审批流程状态跳转等最实际的功能。第一次作业,就是理解这一切的基础。
5.1 从静态页面到请求数据
第一次作业里没有数据,因为你还没有学 ajax 和后端接口。但你可以提前铺垫一个概念:以后页面上的推荐语,可能不是写死在 HTML 里的,而是从服务器接口拿到的。前端要做的事情,变成“请求数据—根据数据渲染列表—将用户操作反馈给后端”。技术工具会变,流程却一样。比如用 jQuery 的 $.ajax 去异步拉取数据,或者在 Vue 中用 axios 获取接口内容,本质上都是在处理同一种交互逻辑。
5.2 “脚本里能运行”和“多人协作里能维护”是两回事
作业阶段,代码只给自己看,所以变量名可以随便写,函数也可以堆成一团。真实项目中代码要给别人看,要给未来的自己看。给自己立几条规则非常有用:
- JavaScript 变量名使用驼峰命名,类名使用小写字母加连字符;
- 每个函数只做一件事,函数名直接说明它做什么,不要写一大堆注释来解释逻辑,代码本身要足够清晰;
- 公共样式尽量抽取,避免同样的颜色在十个地方重复出现,后面调整时会非常痛苦。
这些习惯能让你的代码从“能跑”抬升到“能维护”。在一次作业中可能看不出差距,到了真实项目中,一段结构清晰、命名规范的代码,和一段随意堆叠的代码,团队评审时的信任度是完全不同的。
5.3 第一次作业要不要学框架,这里给你一个判断标准
每次作业结束都有同学问,我现在要不要学 Vue 或 React?我通常反过来问你一个问题:如果让你用原生 JavaScript 写一个简单的“点击按钮更新页面计数”,你能在不出报错的情况下,清晰解释每一步在做什么吗?如果不能,说明你对 DOM 操作、事件机制、作用域的理解仍不牢靠,框架也救不了你。如果能,恭喜你,你已经具备了一定的前端抽象能力,此时踏进框架会非常顺畅。
学习路线有很多,但万变不离其宗:先掌握语言和浏览器这边的运行机制,在此基础上你才可能高效地运用框架的“快捷方式”。一开始就跑到框架的人,一遇到配置问题就会摸不着头脑,往往就是因为不知道框架在底层替你做了哪些事。把第一次作业的知识吃透,远比你多背几个 API 重要。
6. 交作业前必须过的最后一道检查
代码写完了,页面能打开了,交互能跑了,你以为可以提交了?我见过太多作业毁在最后的“小细节”上。在提交之前,请你务必按下 F12 打开开发者工具,一项一项核对下面的清单。
| 检查项 | 操作方式 | 合格标准 |
|---|---|---|
| 页面标题 | 看浏览器标签栏 | 没有写成“无标题文档”,是真实的页面名 |
| 中文编码 | 看页面文字 | 不会出现乱码,确认 meta 里是 UTF-8 |
| 控制台报错 | Console 面板 | 一个红色报错都没有 |
| 移动端显示 | 切换设备工具栏 | 页面在窄屏下不出现横向滚动条 |
| JS 交互 | 逐个点击按钮 | 每个响应都符合预期,不会报错 |
| 资源加载 | Network 面板 | 没有红色失败请求,图片和样式都加载了 |
另外还有一个很细但很能体现态度的点:代码格式化。你可以在 VS Code 中安装 Prettier,右键选择“格式化文档”。让代码的缩进一致、引号统一、换行干净,这在团队协作里是专业度的体现。尤其当你把代码提交到班级群里让同学或助教检查时,一份整洁的代码天然地更容易获得反馈和帮助。
如果你自己检查时发现,移动端有横向滚动条,最简单的排查方法是找到页面上比视口更宽的元素。你可以在 Elements 面板中选中 body,看它的宽度是否超过当前屏幕宽度;如果没超,再逐个选中子元素查看,找到那个“撑破容器”的元素。大多数时候,问题出在一张很宽的图片,或者有一段很长的连续英文字符没有被 word-break: break-all; 正确换行。
我最后再分享一个习惯:作业完成后,不要急着提交,第二天再用十分钟重新看一遍自己的代码。这时候你会发现第一天写的很多地方都很稚嫩,比如某个类名语义不清、某段脚本可以简化。能做代码自省,其实就是编程能力正在提升的信号。这份第一次作业的经验,后面会被你反复用到。把它做扎实,后面的路线自然会顺很多。
