- 本文讲前端三件套:HTML、CSS、JavaScript
- React/Vue 框架入门,讲组件化开发
- 前端渲染模式:CSR、SSR 和 SSG,讲编译产物的区别和服务端渲染
- Next.js 全栈开发入门,讲全栈框架
不管前端框架怎么变、工具链怎么更新,浏览器只认识三样东西:HTML、CSS、JavaScript。React、Vue、Next.js 最终都要编译成这三者才能在浏览器里跑起来。
所以,理解这三件套各自做什么、怎么配合,是学习所有前端技术的起点。
HTML:页面的骨架
HTML(HyperText Markup Language)的作用是定义页面的结构和内容。你可以把它理解为页面的骨架:这里放个标题、那里放个按钮、下面是一段文字。
HTML 由各种标签组成,标签用尖括号包裹,大多数标签成对出现(开始标签 + 结束标签),少数标签(如<br> 换行、<img> 图片、<input> 输入框)不需要结束标签,因为它们不包裹内容:
<h1>这是标题</h1>
<p>这是一段文字</p>
<button>点击我</button>
<h1> 是开始标签,</h1> 是结束标签,中间夹的是内容。浏览器看到 <h1> 就知道这是个一级标题,会把文字渲染得又大又粗。
标签可以嵌套,形成层级关系:
html
<div>
<h2>用户信息</h2>
<p>姓名:张三</p>
<p>职业:工程师</p>
</div>
<div> 是一个通用的容器标签,本身没有视觉效果,纯粹用来把相关的内容分组。你可以把它想象成一个透明的盒子,把几个元素装在一起方便统一管理。
一个完整的 HTML 文档有固定的骨架结构:<!DOCTYPE html> 声明这是 HTML5 文档(固定写法),<html> 是最外层容器,<head> 里放页面元信息(标题、样式引用等,不直接显示),<body> 里放页面可见的内容。
下面这个例子展示了最常见的 HTML 标签,你可以直接编辑左侧代码,右侧会实时显示效果:
HTML 常见标签
html
<!DOCTYPE html>
<html>
<body>
<!-- 标题标签:h1 最大,h6 最小 -->
<h1>一级标题</h1>
<h3>三级标题</h3>
<!-- 段落和文本格式 -->
<p>普通段落,可以包含 <strong>加粗</strong> 和 <em>斜体</em> 文字。</p>
<!-- 链接和图片 -->
<a href="https://labuladong.online">这是一个链接</a>
<br>
<img src="https://placehold.co/200x80?text=HTML" alt="示例图片">
<!-- 列表 -->
<ul>
<li>无序列表项 1</li>
<li>无序列表项 2</li>
</ul>
<!-- 表单元素 -->
<input type="text" placeholder="这是输入框">
<button>这是按钮</button>
<!-- div 容器 -->
<div>
<p>这段内容被 div 包裹,方便分组管理。</p>
</div>
</body>
</html>
你可能注意到了,纯 HTML 页面看起来很朴素:黑字白底,没有任何美化。这就像一栋毛坯房,只有结构没有装修。要好看,得靠 CSS。
标签的属性
标签上可以加属性来提供额外信息。比如<a href="..."> 里的 href 指定链接地址,<img src="..."> 里的 src 指定图片路径。
有两个属性特别重要,后面会频繁用到:
• id: 给元素起一个唯一的名字,方便 JavaScript 精确定位它。一个页面里同一个 id 只能出现一次。
• class: 给元素分类,方便 CSS 批量设置样式。多个元素可以有相同的 class。
html
<div id="user-profile">...</div>
<p class="highlight">这段文字需要高亮</p>
<p class="highlight">这段也需要高亮</p>
类比一下:id 像身份证号,全局唯一;class 像职业标签,多个人可以是同一职业。
DOM 树
浏览器解析 HTML 后,会在内部构建一棵树形结构,叫做 DOM 树(Document Object Model,文档对象模型)。每个标签对应树上的一个元素节点,标签里的文字会成为文本节点,嵌套关系就是父子关系。
比如这段 HTML:
html
<html>
<head></head>
<body>
<h1>一级标题</h1>
<div>
<p>姓名:张三</p>
<p>职业:工程师</p>
</div>
<button>点击</button>
</body>
</html>
浏览器解析后生成的 DOM 树长这样:
text
html
├── head
└── body
├── h1 ("一级标题")
├── div
│ ├── p ("姓名:张三")
│ └── p ("职业:工程师")
└── button ("点击")
标签的嵌套关系变成了树的父子关系,<div> 包裹着两个 <p>,所以 div 是父节点,两个 p 是它的子节点。
这个概念现在了解就行,后面讲 JavaScript 时你会看到,JS 操作页面的本质就是在这棵 DOM 树上进行增删查改,比如给节点改个颜色,加点交互效果等等。
CSS:页面的皮肤
CSS(Cascading Style Sheets)负责页面的视觉呈现:颜色、字体、大小、间距、布局,所有「看起来怎样」的事情都归它管。
CSS 的基本语法是「选择器 + 样式声明」:
css
选择器 {
属性: 值;
属性: 值;
}
选择器决定「改谁的样式」,花括号里的声明决定「改成什么样」。CSS 有三种最常用的选择器:
css
/* 标签选择器:所有 <p> 标签 */
p {
color: gray;
}
/* class 选择器:所有 class="highlight" 的元素(注意前面有个点) */
.highlight {
background-color: yellow;
}
/* id 选择器:id="title" 的那个元素(注意前面有个井号) */
#title {
font-size: 32px;
}
• 标签选择器:管全局默认样式
• class 选择器:管可复用的样式类(实际开发中用得最多)
• id 选择器:管特定的唯一元素
除了在 <style> 标签里用选择器批量设置样式,还可以直接在 HTML 标签上写 style 属性,叫做行内样式(inline style):
html
<p style="color: red; font-size: 20px;">这段文字是红色的</p>
行内样式只对当前这一个元素生效,优先级比选择器更高。一般不推荐大量使用,因为样式散落在各个标签里不好维护。
但后面你会看到,JavaScript 动态修改元素样式时,本质上就是在设置行内样式。
来看个实际效果,下面的例子里,给 HTML 结构加上了 CSS 样式。你可以试着把 <style> 标签里的内容删掉一些,或者直接给 <p> 标签加上 style 属性,观察 HTML 的变化:
CSS 样式效果
html
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: sans-serif;
max-width: 420px;
margin: 0 auto;
padding: 20px;
background: #fafafa;
}
h2 {
color: #333;
border-bottom: 2px solid #4CAF50;
padding-bottom: 8px;
}
/* class 选择器:所有 class="card" 的元素 */
.card {
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
margin: 12px 0;
}
/* .card 内部的 .name 元素(后代选择器) */
.card .name {
font-size: 18px;
font-weight: bold;
color: #333;
}
.card .role {
color: #888;
font-size: 14px;
margin-top: 4px;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
/* 伪类选择器:鼠标悬停时的样式 */
button:hover {
background: #45a049;
}
</style>
</head>
<body>
<h2>团队成员</h2>
<div class="card">
<p class="name">张三</p>
<p class="role">前端工程师</p>
</div>
<div class="card">
<p class="name">李四</p>
<p class="role">后端工程师</p>
</div>
<button>添加成员</button>
</body>
</html>
可以用的 CSS 属性非常多,所有页面样式相关的功能都归 CSS 管,比如颜色、字体、间距、动画、布局自动适配不同尺寸的屏幕等等。
不过现在你只需要理解 CSS 在 HTML 中发挥的作用,没必要去背这些细节了,AI 工具对 CSS 的掌握程度很高,需要的时候给 AI 描述你想要的效果,它就能帮你生成相应的 CSS 代码。
浏览器开发者工具
在继续学 JavaScript 之前,先认识一个重要的工具。在任何网页上按 F12(或右键 → 检查),就能打开浏览器的开发者工具(DevTools)。这是前端开发最核心的调试工具,几个常用面板:
• Elements(元素):查看和实时编辑页面的 HTML 和 CSS。你可以直接修改标签内容、调整样式,效果立即反映在页面上(刷新后恢复)
• Console(控制台):执行 JavaScript 代码,查看报错信息。前端调试最常看的就是这个面板
• Network(网络):查看所有网络请求,包括加载了哪些文件、请求了哪些 API、每个请求花了多长时间
动手试试
现在就在本页面按 F12 打开控制台,依次输入以下命令,观察页面的变化:
javascript
// 查看当前页面标题
document.title
// 把页面所有段落文字变成红色
// 等于给所有 <p> 标签加上 style="color: red;" 属性
document.querySelectorAll('p').forEach(el => el.style.color = 'red')
// 把本文的标题改掉
document.querySelector('h1').textContent = '被我用 JS 改了!'
刷新页面让浏览器重新渲染 HTML,即可撤销这些修改。
你在 Console 里手动执行的代码,和写在 <script> 标签里的代码效果完全一样,区别只是一个手动执行、一个页面加载时自动执行。
JavaScript:页面的灵魂
HTML 搭好了骨架,CSS 画好了皮肤,但页面还是「死」的,点按钮没反应,输入框里填了内容也不知道该干嘛。要让页面「活」起来,就需要 JavaScript。
JavaScript(简称 JS)负责页面的行为和交互逻辑:点击按钮后发生什么、如何从后端获取数据、怎么动态更新页面内容。
JavaScript 的基础语法(变量、函数、条件判断等)可以参考 JavaScript 基础入门系列教程,这里不重复讲。我们重点看 JS 在浏览器里特有的能力:操作 DOM 和 响应用户事件。
JS 代码写在 HTML 的 <script> 标签里,浏览器遇到它就知道要执行代码了。前面说过,浏览器把 HTML 解析成一棵 DOM 树,JavaScript 在浏览器里做的事情,本质上就是在这棵树上增删改查:通过 document.getElementById 找到某个元素,修改它的文本、样式、属性;通过监听 click、input 等事件,响应用户的操作。
来看一个实际例子,感受一下 JavaScript 如何让页面「活」起来:
JavaScript DOM 操作
html
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: sans-serif;
max-width: 400px;
margin: 0 auto;
padding: 20px;
}
.greeting {
font-size: 24px;
color: #333;
margin: 16px 0;
padding: 12px;
background: #f0f0f0;
border-radius: 8px;
min-height: 30px;
}
input {
font-size: 16px;
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 4px;
width: 200px;
}
input:focus {
border-color: #4CAF50;
outline: none;
}
button {
font-size: 14px;
padding: 8px 16px;
margin: 4px;
cursor: pointer;
color: white;
border: none;
border-radius: 4px;
}
.green { background: #4CAF50; }
.blue { background: #2196F3; }
.orange { background: #FF9800; }
.btn-group { margin-top: 12px; }
.color-box {
width: 100%;
height: 40px;
border-radius: 8px;
margin-top: 12px;
/* 颜色变化时有 0.3 秒的过渡动画 */
transition: background-color 0.3s;
background: #e0e0e0;
}
</style>
</head>
<body>
<h3>实时问候</h3>
<input type="text" id="name-input" placeholder="输入你的名字">
<div class="greeting" id="greeting">你好,请输入名字</div>
<h3>切换颜色</h3>
<div class="btn-group">
<button class="green" onclick="changeColor('#4CAF50')">绿色</button>
<button class="blue" onclick="changeColor('#2196F3')">蓝色</button>
<button class="orange" onclick="changeColor('#FF9800')">橙色</button>
</div>
<div class="color-box" id="color-box"></div>
<script>
// 监听输入事件:每次输入内容变化时自动更新问候语
let nameInput = document.getElementById('name-input');
let greeting = document.getElementById('greeting');
nameInput.addEventListener('input', function() {
let name = nameInput.value;
if (name) {
greeting.textContent = '你好,' + name + '!';
} else {
greeting.textContent = '你好,请输入名字';
}
});
// 点击按钮切换颜色
function changeColor(color) {
let box = document.getElementById('color-box');
box.style.backgroundColor = color;
}
</script>
</body>
</html>
如果你习惯了后端或脚本语言的编程模式,初次接触前端 JS 代码可能有点奇怪。
后端代码从第一行开始往下执行,做完一件事接着做下一件。但前端不太一样,页面加载完之后,就一直在等着用户操作,用户做了什么,代码才响应什么。
这就引出了前端编程最核心的模式:回调函数(callback)。
你不是在页面完成加载的时候立即执行这段代码,而是把这段代码「注册」到某个事件上,等事件发生时浏览器会自动调用它。
比如按钮上的 onclick="changeColor('#4CAF50')" 就是一个回调,意思是「当用户点击这个按钮时,执行 changeColor 函数」。你不需要写代码去检测用户有没有点击,浏览器会帮你盯着,点了就调用。
nameInput.addEventListener('input', function() {...}) 也是同样的思路。addEventListener 的意思是给输入框注册一个回调:每当输入内容发生变化,就执行后面这个函数,通过 greeting.textContent = ... 把问候语实时更新到页面上。
回调函数内部做的事情,就是前面说的 DOM 操作。比如 changeColor 函数通过 box.style.backgroundColor = color 给元素设置行内样式,输入框的回调则通过 greeting.textContent = ... 修改元素的文本内容,这样就能在页面上实时看到效果。
到这里,你已经了解了前端三件套各自的职责。接下来看看它们是怎么配合工作的。
三剑客合体
HTML 搭骨架、CSS 画皮肤、JavaScript 加灵魂。来看一个完整的例子,把三者的协作串起来。
下面是一个计数器,功能很简单:点按钮增减数字。但麻雀虽小五脏俱全,HTML、CSS、JS 各司其职:
HTML/CSS/JS 计数器
html
<!DOCTYPE html>
<html>
<head>
<style>
/* CSS:定义样式 */
.container {
text-align: center;
margin-top: 40px;
font-family: sans-serif;
}
.counter {
font-size: 48px;
color: #333;
margin: 20px 0;
}
button {
font-size: 16px;
padding: 10px 20px;
margin: 5px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<!-- HTML:定义结构 -->
<div class="container">
<h1>简单计数器</h1>
<div class="counter" id="count">0</div>
<button onclick="increment()">增加</button>
<button onclick="decrement()">减少</button>
<button onclick="reset()">重置</button>
</div>
<script>
// JavaScript:定义行为
let count = 0;
function increment() {
count++;
updateDisplay();
}
function decrement() {
count--;
updateDisplay();
}
function reset() {
count = 0;
updateDisplay();
}
function updateDisplay() {
document.getElementById('count').textContent = count;
}
</script>
</body>
</html>
串一下这个例子的完整流程:
1 HTML 部分:<button onclick="increment()">增加</button> 创建了一个按钮,onclick 属性告诉浏览器「点击时调用 JavaScript 的 increment() 函数」。<div id="count">0</div> 创建了一个显示数字的区域,id="count" 给它起了个名字,方便 JS 定位。
2 CSS 部分:button { background-color: #4CAF50; color: white; } 让所有按钮变成绿底白字,button:hover 定义了鼠标悬停时背景色变深,.counter { font-size: 48px; } 让数字显示得很大。
3 JavaScript 部分:当用户点击「增加」按钮时,increment() 函数被调用:先执行 count++ 让变量加 1,然后调用 updateDisplay(),通过 document.getElementById('count') 找到那个显示数字的 <div>,把它的文本内容更新为新的数值。
动手试试:把按钮的 background-color 改成 #2196F3(蓝色),然后修改 increment() 函数让每次增加 2,看看效果。
文件分离
上面为了演示方便把 CSS 和 JavaScript 都写在了 HTML 文件里,但实际项目中它们通常是独立的文件。HTML 通过 <link> 引入 CSS,通过 <script src> 引入 JavaScript。
下面把计数器拆成三个独立文件,点击左侧的文件名可以切换查看。功能和前面完全一样,只是代码组织方式变了:
文件分离:HTML + CSS + JS
index.html
html
<!DOCTYPE html>
<html>
<head>
<!-- 引入外部 CSS 文件 -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>简单计数器</h1>
<div class="counter" id="count">0</div>
<button onclick="increment()">增加</button>
<button onclick="decrement()">减少</button>
<button onclick="reset()">重置</button>
</div>
<!-- 引入外部 JavaScript 文件 -->
<script src="app.js"></script>
</body>
</html>
styles.css (内容见上方 <style> 标签内)
app.js (内容见上方 <script> 标签内)
分成独立文件的好处是结构更清晰,而且多个页面可以共享同一个 CSS 或 JS 文件。
浏览器的加载流程
浏览器从上到下解析 HTML,遇到 CSS 和 JS 的引用时发起下载请求,多个文件可以并行下载,但它们对页面渲染的影响不同:
• CSS 会阻塞渲染:浏览器要等 CSS 下载完才开始画页面
• 同步 JS 会阻塞 HTML 解析:遇到 <script> 标签时暂停解析,等脚本下载并执行完才继续
如果你打开浏览器的开发者工具(F12),切到 Network(网络)面板,刷新页面,会看到请求列表:第一个通常是 HTML 文件,紧接着是若干 .css 和 .js 文件。
如果一个网站加载比较慢,你会看到一个典型的过程:
1 首先是一段白屏(浏览器在等 CSS 下载完才开始渲染)
2 然后页面结构和样式一起出现,但点击按钮可能没有反应(JS 还在加载中,交互逻辑还没就绪)
3 最后等 JS 加载完毕,页面才能正常使用。
这也是为什么 <link> 标签通常放在 <head> 里(让 CSS 尽早开始下载),而 <script> 标签通常放在 <body> 末尾或加上 defer 属性(避免阻塞 HTML 解析)。
现代项目更推荐用 <script defer src="app.js"> 放在 <head> 里,浏览器会并行下载脚本,等 HTML 解析完再执行。
小结
浏览器只认识 HTML、CSS、JavaScript 这三样东西。
• HTML 定义页面有什么内容
• CSS 定义这些内容长什么样
• JavaScript 定义用户操作后发生什么
不管前端技术怎么发展、框架怎么更新换代,最终到了浏览器这一层,还是这三者在协作。理解了这个基础,后面学 React、Vue 这些框架就不会迷糊了,因为框架只是帮你更高效地组织这三者,本质没变。