HTML 核心概念与最佳实践
一、文档声明与渲染模式
知识点讲解
DOCTYPE 的作用
DOCTYPE(Document Type Declaration,文档类型声明)用于告诉浏览器当前 HTML 文档采用的规范版本,使浏览器能够按照正确的标准解析和渲染页面。
核心作用:
- 决定浏览器采用标准模式(Standards Mode)还是怪异模式(Quirks Mode)
- 影响 HTML / CSS / JavaScript 的解析行为
- 确保页面在不同浏览器中的一致性
HTML5 标准写法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文档标题</title>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>关键点:
- DOCTYPE 必须写在文档第一行
- HTML5 的 DOCTYPE 非常简洁:
<!DOCTYPE html> - 大小写不敏感,但推荐小写
标准模式 vs 怪异模式
| 对比项 | 标准模式(Standards Mode) | 怪异模式(Quirks Mode) |
|---|---|---|
| 触发条件 | 正确声明 DOCTYPE | 缺失或错误的 DOCTYPE |
| 渲染规则 | 严格按照 W3C 标准 | 宽松、非标准方式 |
| 盒模型 | 标准盒模型(content-box) | 怪异盒模型(border-box) |
| 浏览器兼容 | ✅ 行为一致 | ❌ 不同浏览器差异大 |
| 推荐使用 | ⭐ 必须使用 | ❌ 仅用于兼容旧页面 |
如何检测当前模式
// 在控制台执行
console.log(document.compatMode);
// "CSS1Compat" → 标准模式
// "BackCompat" → 怪异模式📝 要点测验
如果不写 DOCTYPE 会发生什么?
浏览器会进入怪异模式(Quirks Mode),导致:
- 盒模型错乱:
width包含 padding 和 border(类似 IE5) - 样式表现不一致:不同浏览器渲染结果差异巨大
- CSS 特性失效:某些现代 CSS 属性可能不生效
- JavaScript 行为异常:部分 DOM API 行为不符合标准
**结论:**DOCTYPE 是现代网页的必需品,千万不要省略!
为什么 HTML5 的 DOCTYPE 这么简单?
历史对比:
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!-- HTML5 -->
<!DOCTYPE html>原因:
- HTML5 不基于 SGML(Standard Generalized Markup Language),不需要引用 DTD 文件
- HTML5 设计理念是简化、实用、向后兼容
- 只要有 DOCTYPE,浏览器就会触发标准模式,具体值不重要
这是 HTML5 "化繁为简"哲学的典型体现。
二、HTML 语义化
知识点讲解
什么是语义化?
HTML 语义化是指根据内容的结构和含义,选择语义合适的 HTML 标签来组织页面结构,而不是单纯使用 <div> 和 <span> 进行布局。
核心理念:
让正确的标签做正确的事
语义化的四大好处
| 好处 | 说明 | 示例 |
|---|---|---|
| 📖 代码可读性 | 代码结构清晰,易于理解和维护 | 看到 <nav> 就知道是导航 |
| 🔍 SEO 优化 | 搜索引擎更好地理解页面内容 | <article> 中的内容权重更高 |
| ♿ 无障碍访问 | 屏幕阅读器能准确解析页面 | <main> 让盲人用户快速定位主内容 |
| 🎨 默认样式 | 无 CSS 时页面仍有合理结构 | <h1> 自动大而粗 |
常用语义化标签
页面结构类
<header> <!-- 页头:Logo、导航等 -->
<nav> <!-- 导航链接 -->
<main> <!-- 主要内容(页面唯一) -->
<aside> <!-- 侧边栏、广告 -->
<footer> <!-- 页脚:版权、联系方式 -->
<section> <!-- 章节、区块 -->
<article> <!-- 独立完整的内容 -->内容含义类
<h1>-<h6> <!-- 标题层级 -->
<p> <!-- 段落 -->
<strong> <!-- 重要文本(加粗) -->
<em> <!-- 强调文本(斜体) -->
<blockquote> <!-- 引用块 -->
<code> <!-- 代码 -->
<figure> <!-- 图片、图表等独立内容 -->
<figcaption> <!-- figure 的标题 -->
<time> <!-- 时间 -->
<mark> <!-- 高亮文本 -->语义化实战示例
❌ 不推荐(全用 div)
<div class="header">
<div class="nav">
<div class="nav-item">首页</div>
<div class="nav-item">关于</div>
</div>
</div>
<div class="content">
<div class="title">文章标题</div>
<div class="text">文章内容...</div>
</div>✅ 推荐(语义化标签)
<header>
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
<main>
<article>
<h1>文章标题</h1>
<p>文章内容...</p>
</article>
</main>section vs article
核心区别:
| 标签 | 含义 | 独立性 | 使用场景 |
|---|---|---|---|
<article> | 独立、完整、可复用的内容 | ⭐ 高 | 博客文章、新闻、评论、帖子 |
<section> | 文档中的结构性区块 | 中 | 章节、分组、主题区域 |
记忆技巧:
如果这段内容可以单独拿出去发布(如转载到其他网站),用
<article>;否则用<section>
实战案例
博客页面结构
<main>
<!-- article:整篇文章是独立内容 -->
<article>
<header>
<h1>如何学习 HTML5</h1>
<p>作者:张三 | <time datetime="2024-01-08">2024年1月8日</time></p>
</header>
<!-- section:文章内的章节划分 -->
<section>
<h2>什么是 HTML5</h2>
<p>HTML5 是...</p>
</section>
<section>
<h2>HTML5 新特性</h2>
<p>新增了...</p>
</section>
<footer>
<p>标签:<mark>#HTML5</mark> <mark>#前端</mark></p>
</footer>
</article>
<!-- 评论区:每条评论也是独立内容 -->
<section>
<h2>评论</h2>
<article class="comment">
<p>很有帮助!</p>
<footer>—— 李四</footer>
</article>
</section>
</main>📝 要点测验
为什么语义化对 SEO 有帮助?
搜索引擎的工作原理:
- 爬虫抓取:搜索引擎机器人读取网页 HTML
- 内容分析:识别哪些是标题、哪些是正文、哪些是导航
- 权重计算:
<h1>权重 ><p>权重 ><div>权重 - 排名判断:语义清晰的页面更容易获得好排名
实际效果:
<!-- ❌ 搜索引擎不知道这是标题 -->
<div style="font-size: 32px; font-weight: bold;">
如何学习 HTML
</div>
<!-- ✅ 搜索引擎明确知道这是一级标题 -->
<h1>如何学习 HTML</h1>使用 <h1> 的页面在搜索"如何学习 HTML"时排名更靠前。
语义化对无障碍访问(Accessibility)有什么帮助?
屏幕阅读器的工作流程:
- 解析 HTML 结构
- 识别语义标签
- 生成"页面地图"
- 提供快捷导航
示例对比:
<!-- ❌ 屏幕阅读器只能读出"区域" -->
<div>
<div>首页</div>
<div>关于</div>
</div>
<!-- ✅ 屏幕阅读器会说:"导航区域,包含 2 个链接" -->
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>实际帮助:
- 盲人用户可以按
H键跳转到所有标题 - 按
N键跳转到导航 - 按
M键跳转到主内容 - 这些功能只有语义化标签才能实现
三、Meta 标签详解
知识点讲解
Meta 标签的作用
<meta> 标签用于描述 HTML 文档的元数据(metadata),这些信息不会直接显示在页面上,但会被浏览器、搜索引擎、社交媒体等工具读取。
Meta 标签三大类
1️⃣ charset(字符编码)
<meta charset="UTF-8">**作用:**指定 HTML 文档的字符编码格式
| 编码 | 说明 | 使用场景 |
|---|---|---|
| UTF-8 | ⭐ 通用编码,支持全球所有语言 | 现代网页必用 |
| GB2312 | 仅支持简体中文 | 老旧中文网站 |
| GBK | 支持简繁中文 | 传统中文站 |
如果不写会怎样?
<!-- ❌ 没有声明编码 -->
<title>你好世界</title>
<!-- 浏览器可能显示为:浣犲ソ涓栫晫(乱码) -->2️⃣ name 属性(页面描述)
<!-- SEO 必备三件套 -->
<meta name="description" content="这是一篇关于 HTML 的教程">
<meta name="keywords" content="HTML, 前端, 教程">
<meta name="author" content="张三">
<!-- 视口设置(移动端必备) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 搜索引擎爬虫控制 -->
<meta name="robots" content="index, follow">常用 name 值对照表
| name 值 | content 示例 | 作用 |
|---|---|---|
description | "这是一个教程网站" | 搜索结果中的摘要文字 |
keywords | "HTML, CSS, JavaScript" | SEO 关键词(权重已很低) |
author | "张三" | 标识作者 |
viewport | "width=device-width" | 移动端适配 |
robots | "noindex, nofollow" | 禁止搜索引擎索引 |
3️⃣ http-equiv 属性(模拟 HTTP 头)
<!-- 页面刷新与跳转 -->
<meta http-equiv="refresh" content="3;url=https://baidu.com">
<!-- 3秒后跳转到百度 -->
<!-- 缓存控制(不推荐,应该用 HTTP 头) -->
<meta http-equiv="cache-control" content="no-cache">
<!-- 强制使用最新版本渲染(IE 兼容) -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">移动端视口详解
<meta name="viewport" content="width=device-width, initial-scale=1.0">参数拆解:
| 参数 | 值 | 含义 |
|---|---|---|
width | device-width | 视口宽度 = 设备屏幕宽度 |
initial-scale | 1.0 | 初始缩放比例 1:1 |
maximum-scale | 2.0 | 最大缩放倍数 |
minimum-scale | 0.5 | 最小缩放倍数 |
user-scalable | no | 禁止用户缩放(不推荐) |
如果不写会怎样?
没有 viewport → 手机浏览器认为页面宽度是 980px
→ 把整个页面缩小显示在屏幕上
→ 文字极小,需要双指放大才能阅读
→ 响应式布局完全失效社交媒体 Meta 标签(Open Graph)
<!-- 在微信、Facebook、Twitter 分享时的展示 -->
<meta property="og:title" content="文章标题">
<meta property="og:description" content="文章摘要">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta property="og:type" content="article">**效果:**分享到社交媒体时显示精美卡片
📝 要点测验
为什么 UTF-8 是现代网页的标准编码?
UTF-8 的优势:
- 全球通用:支持所有语言(中文、英文、日文、阿拉伯文...)
- 向后兼容 ASCII:英文字符只占 1 字节,节省空间
- 无 BOM 问题:不需要字节顺序标记
- 互联网标准:W3C 推荐,主流框架默认
其他编码的问题:
| 编码 | 问题 |
|---|---|
| GB2312 | 只支持简体中文,无法显示繁体和其他语言 |
| GBK | 中文专用,国际化网站无法使用 |
| UTF-16 | 每个字符至少 2 字节,浪费空间 |
**结论:**2024 年做新项目,无脑选 UTF-8!
meta refresh 和 JavaScript 跳转有什么区别?
三种跳转方式对比:
<!-- 方式1:meta 标签 -->
<meta http-equiv="refresh" content="0;url=/new-page">
<!-- 方式2:JavaScript -->
<script>
window.location.href = '/new-page';
</script>
<!-- 方式3:HTTP 响应头(后端) -->
HTTP/1.1 301 Moved Permanently
Location: /new-page| 方式 | SEO 友好 | 禁用 JS | 可控制 | 推荐度 |
|---|---|---|---|---|
| meta | ⚠️ 中 | ✅ 有效 | ❌ 差 | ⭐⭐ |
| JavaScript | ❌ 差 | ❌ 失效 | ✅ 强 | ⭐⭐⭐ |
| HTTP 301 | ✅ 好 | ✅ 有效 | ✅ 强 | ⭐⭐⭐⭐⭐ |
最佳实践:
- 临时跳转:用 JavaScript
- 永久跳转:用后端 301/302 重定向
- 不推荐 meta refresh(搜索引擎不喜欢)
四、HTML 资源加载
知识点讲解
src vs href 的本质区别
| 属性 | 含义 | 行为 | 示例标签 |
|---|---|---|---|
| src | source(源) | 嵌入资源,替换当前元素 | <img> <script> <iframe> |
| href | hypertext reference(引用) | 建立链接关系 | <a> <link> |
src:资源嵌入
特点:
- 浏览器解析到
src时会暂停 HTML 解析 - 立即下载并执行/渲染资源
- 资源会替换当前元素的内容
示例:
<!-- 图片:下载并显示 -->
<img src="photo.jpg" alt="照片">
<!-- 脚本:下载并执行(阻塞渲染) -->
<script src="app.js"></script>
<!-- iframe:嵌入另一个 HTML 页面 -->
<iframe src="https://example.com"></iframe>性能影响:
<head>
<!-- ❌ 阻塞:浏览器会等待 huge-script.js 下载完再继续 -->
<script src="huge-script.js"></script>
</head>
<body>
<h1>这段文字要等很久才能显示</h1>
</body>href:资源引用
特点:
- 不阻塞 HTML 解析
- 并行下载资源
- 不替换当前元素
示例:
<!-- 链接:点击后跳转 -->
<a href="https://google.com">Google</a>
<!-- 样式表:并行加载 -->
<link rel="stylesheet" href="styles.css">
<!-- 预加载资源 -->
<link rel="preload" href="font.woff2" as="font">script 标签的三种加载方式
1️⃣ 普通 script(默认行为)
<script src="app.js"></script>流程:
HTML 解析 → 遇到 script → 暂停解析 → 下载脚本 → 执行脚本 → 继续解析
▲______________ 阻塞期间页面空白 ______________▼**问题:**页面白屏时间长
2️⃣ async(异步下载,立即执行)
<script src="analytics.js" async></script>流程:
HTML 解析 ────────────────────> 继续解析
↓
异步下载 → 下载完立即执行(可能在 DOM 未完成时)特点:
- ✅ 不阻塞 HTML 解析
- ❌ 执行顺序不确定
- ❌ 可能在 DOM 未完成时执行
适用场景:
<!-- ✅ 适合:独立的第三方脚本 -->
<script src="google-analytics.js" async></script>
<script src="ad-tracker.js" async></script>
<!-- ❌ 不适合:依赖 DOM 的脚本 -->
<script src="main.js" async></script> <!-- 可能找不到 DOM 元素 -->3️⃣ defer(异步下载,延迟执行)
<script src="main.js" defer></script>流程:
HTML 解析 ────────────────────> DOM 完成
↓ ↓
异步下载 ──────────> 按顺序执行所有 defer 脚本
↓
DOMContentLoaded 事件特点:
- ✅ 不阻塞 HTML 解析
- ✅ 执行顺序确定(按 HTML 中出现顺序)
- ✅ 在 DOM 完全解析后执行
- ✅ 在
DOMContentLoaded之前执行
适用场景:
<!-- ✅ 完美:需要操作 DOM 的脚本 -->
<script src="jquery.js" defer></script>
<script src="main.js" defer></script> <!-- 会在 jquery 之后执行 -->三种方式对比表
| 方式 | 下载 | 执行时机 | 执行顺序 | 阻塞解析 | 适用场景 |
|---|---|---|---|---|---|
| 普通 | 立即 | 立即 | 确定 | ✅ 阻塞 | 小脚本、关键脚本 |
| async | 异步 | 下载完立即执行 | ❌ 不确定 | ❌ 不阻塞 | 独立第三方脚本 |
| defer | 异步 | DOM 完成后 | ✅ 确定 | ❌ 不阻塞 | ⭐ 主应用脚本 |
link 标签的 rel 属性
<!-- 引入样式表 -->
<link rel="stylesheet" href="styles.css">
<!-- 网站图标 -->
<link rel="icon" href="favicon.ico">
<!-- 预加载资源(提升性能) -->
<link rel="preload" href="font.woff2" as="font" crossorigin>
<!-- 预连接到域名(加快后续请求) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://cdn.example.com">为什么推荐 link 而不是 @import?
方式对比:
<!-- 方式1:link -->
<link rel="stylesheet" href="styles.css">/* 方式2:@import */
@import url('styles.css');性能对比表:
| 对比项 | link | @import |
|---|---|---|
| 加载时机 | ✅ 页面加载时同步加载 | ❌ 页面加载完成后才加载 |
| 是否阻塞 | 不阻塞(并行加载) | 阻塞后续样式 |
| JS 控制 | ✅ 可通过 JS 添加/删除 | ❌ 无法控制 |
| 性能 | ✅ 更快 | ❌ 更慢 |
| 兼容性 | ✅ 所有浏览器 | ⚠️ IE5+ |
| 优先级 | 高 | 低 |
实际影响:
<!-- link:立即加载,页面快速渲染 -->
<link rel="stylesheet" href="styles.css">/* @import:页面先白屏,然后闪烁一下才显示样式(FOUC) */
@import url('styles.css');FOUC(Flash of Unstyled Content):无样式内容闪烁
📝 要点测验
为什么 script 标签放在 body 底部?
历史原因(传统做法):
<!DOCTYPE html>
<html>
<head>
<!-- ❌ 放 head 里会阻塞页面渲染 -->
<script src="heavy-library.js"></script>
</head>
<body>
<h1>这段文字要等很久才能显示</h1>
<!-- ✅ 放 body 底部,HTML 先渲染 -->
<script src="main.js"></script>
</body>
</html>现代最佳实践(推荐):
<head>
<!-- ✅ 用 defer,既不阻塞又能放 head -->
<script src="main.js" defer></script>
</head>结论:
- 传统项目:放
</body>前 - 现代项目:用
defer放<head> - 第三方脚本:用
async
DOMContentLoaded 和 load 事件有什么区别?
// 事件1:DOM 解析完成
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM 树构建完成,但图片、样式可能还没加载完');
});
// 事件2:所有资源加载完成
window.addEventListener('load', () => {
console.log('页面所有资源(图片、CSS、JS)都加载完成');
});时间线:
开始加载
↓
HTML 解析
↓
DOM 树构建完成
↓
DOMContentLoaded 事件触发 ← defer 脚本在这之前执行
↓
继续加载图片、字体等资源
↓
所有资源加载完成
↓
load 事件触发实际应用:
| 场景 | 使用事件 |
|---|---|
| 操作 DOM(如绑定事件) | DOMContentLoaded |
| 需要图片尺寸信息 | load |
| Vue/React 应用启动 | DOMContentLoaded |
| 页面加载性能统计 | load |
jQuery 对应:
// jQuery 的 ready 就是 DOMContentLoaded
$(document).ready(function() {
// DOM ready
});
// 简写
$(function() {
// DOM ready
});五、HTML 元素分类
知识点讲解
元素的显示类型
HTML 元素根据 display 属性分为三大类:
块级元素(Block)
特点:
- 独占一行
- 宽度默认撑满父容器(
width: 100%) - 可以设置
width、height、margin、padding - 可以包含块级元素和行内元素
常见块级元素:
<div> <!-- 通用容器 -->
<p> <!-- 段落 -->
<h1>-<h6> <!-- 标题 -->
<ul> <ol> <!-- 列表 -->
<li> <!-- 列表项 -->
<header> <!-- 页头 -->
<nav> <!-- 导航 -->
<section> <!-- 区块 -->
<article> <!-- 文章 -->
<footer> <!-- 页脚 -->
<form> <!-- 表单 -->
<table> <!-- 表格 -->行内元素(Inline)
特点:
- 不独占一行,多个元素可以在同一行
- 宽高由内容决定
- ❌ 不能设置
width和height - ⚠️ 设置上下
margin和padding无效(左右有效) - 只能包含文本或其他行内元素
常见行内元素:
<span> <!-- 通用行内容器 -->
<a> <!-- 链接 -->
<strong> <!-- 加粗强调 -->
<em> <!-- 斜体强调 -->
<b> <!-- 粗体 -->
<i> <!-- 斜体 -->
<code> <!-- 代码 -->
<label> <!-- 表单标签 -->
<input> <!-- 输入框 -->
<img> <!-- 图片(特殊行内元素,可设置宽高) -->行内块元素(Inline-Block)
特点:
- ✅ 不独占一行(像行内元素)
- ✅ 可以设置宽高(像块级元素)
- 元素间有空隙(HTML 换行符导致)
示例:
<style>
.inline-block {
display: inline-block;
width: 100px;
height: 100px;
background: skyblue;
}
</style>
<div class="inline-block">1</div>
<div class="inline-block">2</div>
<div class="inline-block">3</div>
<!-- 三个盒子在同一行,且可以设置宽高 -->常见行内块元素:
<img> <!-- 图片 -->
<input> <!-- 输入框 -->
<button> <!-- 按钮 -->
<select> <!-- 下拉框 -->三种类型对比表
| 特性 | Block | Inline | Inline-Block |
|---|---|---|---|
| 独占一行 | ✅ | ❌ | ❌ |
| 可设置 width/height | ✅ | ❌ | ✅ |
| 上下 margin/padding | ✅ | ❌ | ✅ |
| 左右 margin/padding | ✅ | ✅ | ✅ |
| 默认宽度 | 100% | 内容决定 | 内容决定 |
| 可包含块级元素 | ✅ | ❌ | ✅ |
相互转换
/* 块级 → 行内 */
div {
display: inline;
}
/* 行内 → 块级 */
span {
display: block;
}
/* 任意 → 行内块 */
.element {
display: inline-block;
}
/* 现代布局(推荐) */
.container {
display: flex; /* 或 grid */
}📝 要点测验
为什么 img 是行内元素,却可以设置宽高?
特殊的行内元素:
<img>、<input>、<button> 等元素被称为替换元素(Replaced Element),它们有特殊的渲染机制:
<img src="photo.jpg" width="200" height="150">
<!-- 虽然是 inline,但可以设置宽高 -->原因:
- 这些元素的内容不是由 CSS 决定的,而是由外部资源(图片文件、表单控件)决定
- 浏览器给它们特殊待遇:默认
display: inline,但表现得像inline-block
验证:
img {
display: inline; /* 默认值 */
width: 100px; /* ✅ 有效 */
height: 100px; /* ✅ 有效 */
margin: 20px; /* ✅ 上下左右都有效 */
}结论:<img> 虽然默认是 inline,但它是一个"伪装的 inline-block"。
行内元素之间为什么有空隙?如何消除?
问题演示:
<span>A</span>
<span>B</span>
<span>C</span>
<!-- A B C 之间会有小空隙 -->原因:
- HTML 中的换行符和空格会被浏览器解析为一个空格
- 行内元素会保留这些空格
解决方案:
<!-- 方案1:不换行(不推荐,可读性差) -->
<span>A</span><span>B</span><span>C</span>
<!-- 方案2:HTML 注释消除换行 -->
<span>A</span><!--
--><span>B</span><!--
--><span>C</span>
<!-- 方案3:父元素 font-size: 0(推荐) -->
<style>
.container {
font-size: 0;
}
.container span {
font-size: 16px;
}
</style>
<div class="container">
<span>A</span>
<span>B</span>
<span>C</span>
</div>
<!-- 方案4:用 Flexbox(最推荐) -->
<style>
.container {
display: flex;
gap: 0;
}
</style>
<div class="container">
<span>A</span>
<span>B</span>
<span>C</span>
</div>**现代最佳实践:**能用 Flexbox/Grid 就不要用 inline-block。
六、浏览器渲染流程
知识点讲解
从 URL 到页面显示的完整过程
渲染流程五大步骤
1. 解析 HTML → DOM 树
↓
2. 解析 CSS → CSSOM 树
↓
3. DOM + CSSOM → Render Tree(渲染树)
↓
4. Layout(布局/回流):计算元素位置和大小
↓
5. Paint(绘制/重绘):绘制像素到屏幕步骤详解
1️⃣ 构建 DOM 树
<html>
<body>
<div>
<p>Hello</p>
</div>
</body>
</html>转换为 DOM 树:
html
└─ body
└─ div
└─ p
└─ "Hello"2️⃣ 构建 CSSOM 树
body { font-size: 16px; }
div { padding: 10px; }
p { color: red; }转换为 CSSOM 树:
body { font-size: 16px }
└─ div { font-size: 16px, padding: 10px }
└─ p { font-size: 16px, padding: 10px, color: red }**注意:**CSSOM 会继承父元素样式
3️⃣ 合并为 Render Tree
DOM + CSSOM = Render Tree
- 只包含可见元素(
display: none的元素不在渲染树中) visibility: hidden的元素在渲染树中(占据空间,只是不可见)
示例:
<div class="visible">可见</div>
<div class="hidden1">display none</div>
<div class="hidden2">visibility hidden</div>.hidden1 { display: none; } /* ❌ 不在渲染树 */
.hidden2 { visibility: hidden; } /* ✅ 在渲染树,占位 */4️⃣ Layout(布局/回流)
计算每个元素的:
- 位置(x, y 坐标)
- 尺寸(width, height)
- 盒模型(margin, padding, border)
触发回流的操作(性能杀手):
// ❌ 会触发回流的操作
element.style.width = '100px';
element.style.height = '100px';
element.classList.add('big-box');
document.body.appendChild(newElement);
// ❌ 读取布局信息也会触发回流
const width = element.offsetWidth;
const height = element.clientHeight;
const rect = element.getBoundingClientRect();5️⃣ Paint(绘制/重绘)
将渲染树的每个节点转换为屏幕上的实际像素
绘制内容:
- 文字
- 颜色
- 图片
- 边框
- 阴影
触发重绘的操作(性能影响较小):
// ✅ 只触发重绘,不触发回流
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.visibility = 'hidden';回流 vs 重绘
| 对比项 | 回流(Reflow) | 重绘(Repaint) |
|---|---|---|
| 英文名 | Reflow / Layout | Repaint |
| 触发条件 | 改变元素几何属性 | 改变元素外观属性 |
| 影响范围 | 整个文档树 | 仅当前元素 |
| 性能影响 | ⚠️ 非常大 | 较小 |
| 是否必然重绘 | ✅ 回流必然重绘 | ❌ 重绘不一定回流 |
触发回流的操作清单
DOM 操作:
element.appendChild(child); // 添加元素
element.removeChild(child); // 删除元素
element.style.display = 'none'; // 显示/隐藏样式修改:
element.style.width = '100px'; // 宽高
element.style.padding = '10px'; // 内边距
element.style.margin = '10px'; // 外边距
element.style.border = '1px'; // 边框
element.style.fontSize = '16px'; // 字号(影响高度)读取布局信息:
element.offsetWidth / offsetHeight
element.clientWidth / clientHeight
element.scrollWidth / scrollHeight
element.getBoundingClientRect()
window.getComputedStyle(element)性能优化技巧
1️⃣ 批量修改样式
// ❌ 错误:每次修改都触发回流
element.style.width = '100px';
element.style.height = '100px';
element.style.padding = '10px';
// ✅ 正确:通过 class 一次性修改
element.className = 'big-box';2️⃣ 使用文档片段
// ❌ 错误:每次 append 都触发回流
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
}
// ✅ 正确:使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
document.body.appendChild(fragment); // 只触发 1 次回流3️⃣ 读写分离
// ❌ 错误:读写交替,触发多次回流
const width1 = element1.offsetWidth; // 读(触发回流)
element1.style.width = width1 + 10 + 'px'; // 写
const width2 = element2.offsetWidth; // 读(又触发回流)
element2.style.width = width2 + 10 + 'px'; // 写
// ✅ 正确:先读后写
const width1 = element1.offsetWidth;
const width2 = element2.offsetWidth;
element1.style.width = width1 + 10 + 'px';
element2.style.width = width2 + 10 + 'px';4️⃣ 使用 transform
/* ❌ 会触发回流 */
.move {
position: absolute;
left: 100px;
top: 100px;
}
/* ✅ 只触发合成(Composite),不回流不重绘 */
.move {
transform: translate(100px, 100px);
}GPU 加速的属性:
transformopacityfilter
📝 要点测验
为什么 display: none 和 visibility: hidden 对性能影响不同?
对比:
/* 方式1:display: none */
.hidden1 {
display: none;
}
/* 方式2:visibility: hidden */
.hidden2 {
visibility: hidden;
}渲染流程对比:
| 步骤 | display: none | visibility: hidden |
|---|---|---|
| DOM 树 | ✅ 存在 | ✅ 存在 |
| 渲染树 | ❌ 不存在 | ✅ 存在 |
| Layout | ❌ 不计算 | ✅ 计算并占位 |
| Paint | ❌ 不绘制 | ❌ 不绘制 |
| 触发回流 | ✅ 会(因为改变了布局) | ❌ 不会(只是不可见) |
性能影响:
// ⚠️ display 切换会触发回流(昂贵)
element.style.display = 'none';
element.style.display = 'block'; // 重新计算布局
// ✅ visibility 切换只触发重绘(便宜)
element.style.visibility = 'hidden';
element.style.visibility = 'visible'; // 只是重新绘制使用建议:
- 频繁切换:用
visibility或opacity - 彻底隐藏:用
display: none - 动画效果:用
opacity+transform
为什么 transform 比 left/top 性能好?
动画性能对比:
/* ❌ 方案1:修改 left/top */
@keyframes move-bad {
from { left: 0; }
to { left: 100px; }
}
/* ✅ 方案2:使用 transform */
@keyframes move-good {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}渲染成本对比:
| 属性 | Layout | Paint | Composite | 性能 |
|---|---|---|---|---|
| left/top | ✅ 触发 | ✅ 触发 | ✅ 触发 | ❌ 差 |
| transform | ❌ | ❌ | ✅ 触发 | ✅ 好 |
| opacity | ❌ | ❌ | ✅ 触发 | ✅ 好 |
原理:
transform 和 opacity 会创建新的合成层(Composite Layer),由 GPU 处理,不影响主线程的布局和绘制。
Chrome DevTools 查看:
F12 → More tools → Rendering → Layer borders可以看到使用 transform 的元素会单独成为一层(橙色边框)。
最佳实践:
- 动画用
transform+opacity - 避免动画
width、height、left、top - 大量元素动画用 CSS 而不是 JavaScript
总结:HTML 核心知识速查表
| 主题 | 核心要点 | 最佳实践 |
|---|---|---|
| DOCTYPE | 必须写在第一行 | HTML5 用 <!DOCTYPE html> |
| 语义化 | 用对标签,提升 SEO 和可访问性 | <article> <nav> <main> |
| Meta 标签 | charset、viewport、description | 必须有 UTF-8 和 viewport |
| 资源加载 | src 阻塞,href 不阻塞 | script 用 defer |
| 元素类型 | block、inline、inline-block | 现代布局用 Flex/Grid |
| 渲染流程 | DOM → CSSOM → Render Tree → Layout → Paint | 减少回流,动画用 transform |
学习建议
- ⭐ 面试必背:DOCTYPE 作用、语义化、src vs href、async vs defer
- ⭐ 重点理解:浏览器渲染流程、回流与重绘
- ⭐ 实战必备:Meta 标签配置、script 加载优化
- ⭐ 性能优化:减少回流、使用 transform、批量操作 DOM