- 本文講前端三件套:HTML、CSS、JavaScript
- React/Vue 框架入門,講組件化開發(fā)
- 前端渲染模式:CSR、SSR 和 SSG,講編譯產物的區(qū)別和服務端渲染
- Next.js 全棧開發(fā)入門,講全??蚣?/li>
不管前端框架怎么變、工具鏈怎么更新,瀏覽器只認識三樣東西:HTML、CSS、JavaScript。React、Vue、Next.js 最終都要編譯成這三者才能在瀏覽器里跑起來。
所以,理解這三件套各自做什么、怎么配合,是學習所有前端技術的起點。
HTML:頁面的骨架
HTML(HyperText Markup Language)的作用是定義頁面的結構和內容。你可以把它理解為頁面的骨架:這里放個標題、那里放個按鈕、下面是一段文字。
HTML 由各種標簽組成,標簽用尖括號包裹,大多數(shù)標簽成對出現(xiàn)(開始標簽 + 結束標簽),少數(shù)標簽(如<br> 換行、<img> 圖片、<input> 輸入框)不需要結束標簽,因為它們不包裹內容:
<h1>這是標題</h1>
<p>這是一段文字</p>
<button>點擊我</button>
<h1> 是開始標簽,</h1> 是結束標簽,中間夾的是內容。瀏覽器看到 <h1> 就知道這是個一級標題,會把文字渲染得又大又粗。
標簽可以嵌套,形成層級關系:
html
<div>
<h2>用戶信息</h2>
<p>姓名:張三</p>
<p>職業(yè):工程師</p>
</div>
<div> 是一個通用的容器標簽,本身沒有視覺效果,純粹用來把相關的內容分組。你可以把它想象成一個透明的盒子,把幾個元素裝在一起方便統(tǒng)一管理。
一個完整的 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 >這是一個鏈接</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 只能出現(xiàn)一次。
? class: 給元素分類,方便 CSS 批量設置樣式。多個元素可以有相同的 class。
html
<div id="user-profile">...</div>
<p class="highlight">這段文字需要高亮</p>
<p class="highlight">這段也需要高亮</p>
類比一下:id 像身份證號,全局唯一;class 像職業(yè)標簽,多個人可以是同一職業(yè)。
DOM 樹
瀏覽器解析 HTML 后,會在內部構建一棵樹形結構,叫做 DOM 樹(Document Object Model,文檔對象模型)。每個標簽對應樹上的一個元素節(jié)點,標簽里的文字會成為文本節(jié)點,嵌套關系就是父子關系。
比如這段 HTML:
html
<html>
<head></head>
<body>
<h1>一級標題</h1>
<div>
<p>姓名:張三</p>
<p>職業(yè):工程師</p>
</div>
<button>點擊</button>
</body>
</html>
瀏覽器解析后生成的 DOM 樹長這樣:
text
html
├── head
└── body
├── h1 ("一級標題")
├── div
│ ├── p ("姓名:張三")
│ └── p ("職業(yè):工程師")
└── button ("點擊")
標簽的嵌套關系變成了樹的父子關系,<div> 包裹著兩個 <p>,所以 div 是父節(jié)點,兩個 p 是它的子節(jié)點。
這個概念現(xiàn)在了解就行,后面講 JavaScript 時你會看到,JS 操作頁面的本質就是在這棵 DOM 樹上進行增刪查改,比如給節(jié)點改個顏色,加點交互效果等等。
CSS:頁面的皮膚
CSS(Cascading Style Sheets)負責頁面的視覺呈現(xiàn):顏色、字體、大小、間距、布局,所有「看起來怎樣」的事情都歸它管。
CSS 的基本語法是「選擇器 + 樣式聲明」:
css
選擇器 {
屬性: 值;
屬性: 值;
}
選擇器決定「改誰的樣式」,花括號里的聲明決定「改成什么樣」。CSS 有三種最常用的選擇器:
css
/* 標簽選擇器:所有 <p> 標簽 */
p {
color: gray;
}
/* class 選擇器:所有 class="highlight" 的元素(注意前面有個點) */
.highlight {
background-color: yellow;
}
/* id 選擇器:id="title" 的那個元素(注意前面有個井號) */
#title {
font-size: 32px;
}
? 標簽選擇器:管全局默認樣式
? class 選擇器:管可復用的樣式類(實際開發(fā)中用得最多)
? id 選擇器:管特定的唯一元素
除了在 <style> 標簽里用選擇器批量設置樣式,還可以直接在 HTML 標簽上寫 style 屬性,叫做行內樣式(inline style):
html
<p style="color: red; font-size: 20px;">這段文字是紅色的</p>
行內樣式只對當前這一個元素生效,優(yōu)先級比選擇器更高。一般不推薦大量使用,因為樣式散落在各個標簽里不好維護。
但后面你會看到,JavaScript 動態(tài)修改元素樣式時,本質上就是在設置行內樣式。
來看個實際效果,下面的例子里,給 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 管,比如顏色、字體、間距、動畫、布局自動適配不同尺寸的屏幕等等。
不過現(xiàn)在你只需要理解 CSS 在 HTML 中發(fā)揮的作用,沒必要去背這些細節(jié)了,AI 工具對 CSS 的掌握程度很高,需要的時候給 AI 描述你想要的效果,它就能幫你生成相應的 CSS 代碼。
瀏覽器開發(fā)者工具
在繼續(xù)學 JavaScript 之前,先認識一個重要的工具。在任何網頁上按 F12(或右鍵 → 檢查),就能打開瀏覽器的開發(fā)者工具(DevTools)。這是前端開發(fā)最核心的調試工具,幾個常用面板:
? Elements(元素):查看和實時編輯頁面的 HTML 和 CSS。你可以直接修改標簽內容、調整樣式,效果立即反映在頁面上(刷新后恢復)
? Console(控制臺):執(zhí)行 JavaScript 代碼,查看報錯信息。前端調試最??吹木褪沁@個面板
? Network(網絡):查看所有網絡請求,包括加載了哪些文件、請求了哪些 API、每個請求花了多長時間
動手試試
現(xiàn)在就在本頁面按 F12 打開控制臺,依次輸入以下命令,觀察頁面的變化:
javascript
// 查看當前頁面標題
document.title
// 把頁面所有段落文字變成紅色
// 等于給所有 <p> 標簽加上 style="color: red;" 屬性
document.querySelectorAll('p').forEach(el => el.style.color = 'red')
// 把本文的標題改掉
document.querySelector('h1').textContent = '被我用 JS 改了!'
刷新頁面讓瀏覽器重新渲染 HTML,即可撤銷這些修改。
你在 Console 里手動執(zhí)行的代碼,和寫在 <script> 標簽里的代碼效果完全一樣,區(qū)別只是一個手動執(zhí)行、一個頁面加載時自動執(zhí)行。
JavaScript:頁面的靈魂
HTML 搭好了骨架,CSS 畫好了皮膚,但頁面還是「死」的,點按鈕沒反應,輸入框里填了內容也不知道該干嘛。要讓頁面「活」起來,就需要 JavaScript。
JavaScript(簡稱 JS)負責頁面的行為和交互邏輯:點擊按鈕后發(fā)生什么、如何從后端獲取數(shù)據(jù)、怎么動態(tài)更新頁面內容。
JavaScript 的基礎語法(變量、函數(shù)、條件判斷等)可以參考 JavaScript 基礎入門系列教程,這里不重復講。我們重點看 JS 在瀏覽器里特有的能力:操作 DOM 和 響應用戶事件。
JS 代碼寫在 HTML 的 <script> 標簽里,瀏覽器遇到它就知道要執(zhí)行代碼了。前面說過,瀏覽器把 HTML 解析成一棵 DOM 樹,JavaScript 在瀏覽器里做的事情,本質上就是在這棵樹上增刪改查:通過 document.getElementById 找到某個元素,修改它的文本、樣式、屬性;通過監(jiān)聽 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>
// 監(jiān)聽輸入事件:每次輸入內容變化時自動更新問候語
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 代碼可能有點奇怪。
后端代碼從第一行開始往下執(zhí)行,做完一件事接著做下一件。但前端不太一樣,頁面加載完之后,就一直在等著用戶操作,用戶做了什么,代碼才響應什么。
這就引出了前端編程最核心的模式:回調函數(shù)(callback)。
你不是在頁面完成加載的時候立即執(zhí)行這段代碼,而是把這段代碼「注冊」到某個事件上,等事件發(fā)生時瀏覽器會自動調用它。
比如按鈕上的 onclick="changeColor('#4CAF50')" 就是一個回調,意思是「當用戶點擊這個按鈕時,執(zhí)行 changeColor 函數(shù)」。你不需要寫代碼去檢測用戶有沒有點擊,瀏覽器會幫你盯著,點了就調用。
nameInput.addEventListener('input', function() {...}) 也是同樣的思路。addEventListener 的意思是給輸入框注冊一個回調:每當輸入內容發(fā)生變化,就執(zhí)行后面這個函數(shù),通過 greeting.textContent = ... 把問候語實時更新到頁面上。
回調函數(shù)內部做的事情,就是前面說的 DOM 操作。比如 changeColor 函數(shù)通過 box.style.backgroundColor = color 給元素設置行內樣式,輸入框的回調則通過 greeting.textContent = ... 修改元素的文本內容,這樣就能在頁面上實時看到效果。
到這里,你已經了解了前端三件套各自的職責。接下來看看它們是怎么配合工作的。
三劍客合體
HTML 搭骨架、CSS 畫皮膚、JavaScript 加靈魂。來看一個完整的例子,把三者的協(xié)作串起來。
下面是一個計數(shù)器,功能很簡單:點按鈕增減數(shù)字。但麻雀雖小五臟俱全,HTML、CSS、JS 各司其職:
HTML/CSS/JS 計數(shù)器
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>簡單計數(shù)器</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> 創(chuàng)建了一個按鈕,onclick 屬性告訴瀏覽器「點擊時調用 JavaScript 的 increment() 函數(shù)」。<div id="count">0</div> 創(chuàng)建了一個顯示數(shù)字的區(qū)域,id="count" 給它起了個名字,方便 JS 定位。
2 CSS 部分:button { background-color: #4CAF50; color: white; } 讓所有按鈕變成綠底白字,button:hover 定義了鼠標懸停時背景色變深,.counter { font-size: 48px; } 讓數(shù)字顯示得很大。
3 JavaScript 部分:當用戶點擊「增加」按鈕時,increment() 函數(shù)被調用:先執(zhí)行 count++ 讓變量加 1,然后調用 updateDisplay(),通過 document.getElementById('count') 找到那個顯示數(shù)字的 <div>,把它的文本內容更新為新的數(shù)值。
動手試試:把按鈕的 background-color 改成 #2196F3(藍色),然后修改 increment() 函數(shù)讓每次增加 2,看看效果。
文件分離
上面為了演示方便把 CSS 和 JavaScript 都寫在了 HTML 文件里,但實際項目中它們通常是獨立的文件。HTML 通過 <link> 引入 CSS,通過 <script src> 引入 JavaScript。
下面把計數(shù)器拆成三個獨立文件,點擊左側的文件名可以切換查看。功能和前面完全一樣,只是代碼組織方式變了:
文件分離:HTML + CSS + JS
index.html
html
<!DOCTYPE html>
<html>
<head>
<!-- 引入外部 CSS 文件 -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>簡單計數(shù)器</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 的引用時發(fā)起下載請求,多個文件可以并行下載,但它們對頁面渲染的影響不同:
? CSS 會阻塞渲染:瀏覽器要等 CSS 下載完才開始畫頁面
? 同步 JS 會阻塞 HTML 解析:遇到 <script> 標簽時暫停解析,等腳本下載并執(zhí)行完才繼續(xù)
如果你打開瀏覽器的開發(fā)者工具(F12),切到 Network(網絡)面板,刷新頁面,會看到請求列表:第一個通常是 HTML 文件,緊接著是若干 .css 和 .js 文件。
如果一個網站加載比較慢,你會看到一個典型的過程:
1 首先是一段白屏(瀏覽器在等 CSS 下載完才開始渲染)
2 然后頁面結構和樣式一起出現(xiàn),但點擊按鈕可能沒有反應(JS 還在加載中,交互邏輯還沒就緒)
3 最后等 JS 加載完畢,頁面才能正常使用。
這也是為什么 <link> 標簽通常放在 <head> 里(讓 CSS 盡早開始下載),而 <script> 標簽通常放在 <body> 末尾或加上 defer 屬性(避免阻塞 HTML 解析)。
現(xiàn)代項目更推薦用 <script defer src="app.js"> 放在 <head> 里,瀏覽器會并行下載腳本,等 HTML 解析完再執(zhí)行。
小結
瀏覽器只認識 HTML、CSS、JavaScript 這三樣東西。
? HTML 定義頁面有什么內容
? CSS 定義這些內容長什么樣
? JavaScript 定義用戶操作后發(fā)生什么
不管前端技術怎么發(fā)展、框架怎么更新?lián)Q代,最終到了瀏覽器這一層,還是這三者在協(xié)作。理解了這個基礎,后面學 React、Vue 這些框架就不會迷糊了,因為框架只是幫你更高效地組織這三者,本質沒變。