前端基础

  • 本文讲前端三件套: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 这些框架就不会迷糊了,因为框架只是帮你更高效地组织这三者,本质没变。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容