Skip to content

HTML 核心概念与最佳实践


一、文档声明与渲染模式

知识点讲解

DOCTYPE 的作用

DOCTYPE(Document Type Declaration,文档类型声明)用于告诉浏览器当前 HTML 文档采用的规范版本,使浏览器能够按照正确的标准解析和渲染页面。

核心作用:

  • 决定浏览器采用标准模式(Standards Mode)还是怪异模式(Quirks Mode)
  • 影响 HTML / CSS / JavaScript 的解析行为
  • 确保页面在不同浏览器中的一致性

HTML5 标准写法

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>
</head>
<body>
  <!-- 页面内容 -->
</body>
</html>

关键点:

  • DOCTYPE 必须写在文档第一行
  • HTML5 的 DOCTYPE 非常简洁:<!DOCTYPE html>
  • 大小写不敏感,但推荐小写

标准模式 vs 怪异模式

对比项标准模式(Standards Mode)怪异模式(Quirks Mode)
触发条件正确声明 DOCTYPE缺失或错误的 DOCTYPE
渲染规则严格按照 W3C 标准宽松、非标准方式
盒模型标准盒模型(content-box)怪异盒模型(border-box)
浏览器兼容✅ 行为一致❌ 不同浏览器差异大
推荐使用⭐ 必须使用❌ 仅用于兼容旧页面

如何检测当前模式

javascript
// 在控制台执行
console.log(document.compatMode);
// "CSS1Compat" → 标准模式
// "BackCompat"  → 怪异模式

📝 要点测验

如果不写 DOCTYPE 会发生什么?

浏览器会进入怪异模式(Quirks Mode),导致:

  1. 盒模型错乱width 包含 padding 和 border(类似 IE5)
  2. 样式表现不一致:不同浏览器渲染结果差异巨大
  3. CSS 特性失效:某些现代 CSS 属性可能不生效
  4. JavaScript 行为异常:部分 DOM API 行为不符合标准

**结论:**DOCTYPE 是现代网页的必需品,千万不要省略!

为什么 HTML5 的 DOCTYPE 这么简单?

历史对比:

html
<!-- 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> 自动大而粗

常用语义化标签

页面结构类

html
<header>   <!-- 页头:Logo、导航等 -->
<nav>      <!-- 导航链接 -->
<main>     <!-- 主要内容(页面唯一) -->
<aside>    <!-- 侧边栏、广告 -->
<footer>   <!-- 页脚:版权、联系方式 -->
<section>  <!-- 章节、区块 -->
<article>  <!-- 独立完整的内容 -->

内容含义类

html
<h1>-<h6>  <!-- 标题层级 -->
<p>        <!-- 段落 -->
<strong>   <!-- 重要文本(加粗) -->
<em>       <!-- 强调文本(斜体) -->
<blockquote> <!-- 引用块 -->
<code>     <!-- 代码 -->
<figure>   <!-- 图片、图表等独立内容 -->
<figcaption> <!-- figure 的标题 -->
<time>     <!-- 时间 -->
<mark>     <!-- 高亮文本 -->

语义化实战示例

❌ 不推荐(全用 div)

html
<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>

✅ 推荐(语义化标签)

html
<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>


实战案例

博客页面结构

html
<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 有帮助?

搜索引擎的工作原理:

  1. 爬虫抓取:搜索引擎机器人读取网页 HTML
  2. 内容分析:识别哪些是标题、哪些是正文、哪些是导航
  3. 权重计算<h1> 权重 > <p> 权重 > <div> 权重
  4. 排名判断:语义清晰的页面更容易获得好排名

实际效果:

html
<!-- ❌ 搜索引擎不知道这是标题 -->
<div style="font-size: 32px; font-weight: bold;">
  如何学习 HTML
</div>

<!-- ✅ 搜索引擎明确知道这是一级标题 -->
<h1>如何学习 HTML</h1>

使用 <h1> 的页面在搜索"如何学习 HTML"时排名更靠前。

语义化对无障碍访问(Accessibility)有什么帮助?

屏幕阅读器的工作流程:

  1. 解析 HTML 结构
  2. 识别语义标签
  3. 生成"页面地图"
  4. 提供快捷导航

示例对比:

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(字符编码)

html
<meta charset="UTF-8">

**作用:**指定 HTML 文档的字符编码格式

编码说明使用场景
UTF-8⭐ 通用编码,支持全球所有语言现代网页必用
GB2312仅支持简体中文老旧中文网站
GBK支持简繁中文传统中文站

如果不写会怎样?

html
<!-- ❌ 没有声明编码 -->
<title>你好世界</title>
<!-- 浏览器可能显示为:浣犲ソ涓栫晫(乱码) -->

2️⃣ name 属性(页面描述)

html
<!-- 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 头)

html
<!-- 页面刷新与跳转 -->
<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">

移动端视口详解

html
<meta name="viewport" content="width=device-width, initial-scale=1.0">

参数拆解:

参数含义
widthdevice-width视口宽度 = 设备屏幕宽度
initial-scale1.0初始缩放比例 1:1
maximum-scale2.0最大缩放倍数
minimum-scale0.5最小缩放倍数
user-scalableno禁止用户缩放(不推荐)

如果不写会怎样?

没有 viewport → 手机浏览器认为页面宽度是 980px
→ 把整个页面缩小显示在屏幕上
→ 文字极小,需要双指放大才能阅读
→ 响应式布局完全失效

社交媒体 Meta 标签(Open Graph)

html
<!-- 在微信、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 的优势:

  1. 全球通用:支持所有语言(中文、英文、日文、阿拉伯文...)
  2. 向后兼容 ASCII:英文字符只占 1 字节,节省空间
  3. 无 BOM 问题:不需要字节顺序标记
  4. 互联网标准:W3C 推荐,主流框架默认

其他编码的问题:

编码问题
GB2312只支持简体中文,无法显示繁体和其他语言
GBK中文专用,国际化网站无法使用
UTF-16每个字符至少 2 字节,浪费空间

**结论:**2024 年做新项目,无脑选 UTF-8!

meta refresh 和 JavaScript 跳转有什么区别?

三种跳转方式对比:

html
<!-- 方式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 的本质区别

属性含义行为示例标签
srcsource(源)嵌入资源,替换当前元素<img> <script> <iframe>
hrefhypertext reference(引用)建立链接关系<a> <link>

src:资源嵌入

特点:

  • 浏览器解析到 src 时会暂停 HTML 解析
  • 立即下载并执行/渲染资源
  • 资源会替换当前元素的内容

示例:

html
<!-- 图片:下载并显示 -->
<img src="photo.jpg" alt="照片">

<!-- 脚本:下载并执行(阻塞渲染) -->
<script src="app.js"></script>

<!-- iframe:嵌入另一个 HTML 页面 -->
<iframe src="https://example.com"></iframe>

性能影响:

html
<head>
  <!-- ❌ 阻塞:浏览器会等待 huge-script.js 下载完再继续 -->
  <script src="huge-script.js"></script>
</head>
<body>
  <h1>这段文字要等很久才能显示</h1>
</body>

href:资源引用

特点:

  • 不阻塞 HTML 解析
  • 并行下载资源
  • 不替换当前元素

示例:

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(默认行为)

html
<script src="app.js"></script>

流程:

HTML 解析 → 遇到 script → 暂停解析 → 下载脚本 → 执行脚本 → 继续解析
         ▲______________ 阻塞期间页面空白 ______________▼

**问题:**页面白屏时间长


2️⃣ async(异步下载,立即执行)

html
<script src="analytics.js" async></script>

流程:

HTML 解析 ────────────────────> 继续解析

         异步下载 → 下载完立即执行(可能在 DOM 未完成时)

特点:

  • ✅ 不阻塞 HTML 解析
  • ❌ 执行顺序不确定
  • ❌ 可能在 DOM 未完成时执行

适用场景:

html
<!-- ✅ 适合:独立的第三方脚本 -->
<script src="google-analytics.js" async></script>
<script src="ad-tracker.js" async></script>

<!-- ❌ 不适合:依赖 DOM 的脚本 -->
<script src="main.js" async></script> <!-- 可能找不到 DOM 元素 -->

3️⃣ defer(异步下载,延迟执行)

html
<script src="main.js" defer></script>

流程:

HTML 解析 ────────────────────> DOM 完成
         ↓                          ↓
         异步下载 ──────────> 按顺序执行所有 defer 脚本

                               DOMContentLoaded 事件

特点:

  • ✅ 不阻塞 HTML 解析
  • ✅ 执行顺序确定(按 HTML 中出现顺序)
  • ✅ 在 DOM 完全解析后执行
  • ✅ 在 DOMContentLoaded 之前执行

适用场景:

html
<!-- ✅ 完美:需要操作 DOM 的脚本 -->
<script src="jquery.js" defer></script>
<script src="main.js" defer></script> <!-- 会在 jquery 之后执行 -->

三种方式对比表

方式下载执行时机执行顺序阻塞解析适用场景
普通立即立即确定✅ 阻塞小脚本、关键脚本
async异步下载完立即执行❌ 不确定❌ 不阻塞独立第三方脚本
defer异步DOM 完成后✅ 确定❌ 不阻塞⭐ 主应用脚本

html
<!-- 引入样式表 -->
<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">

方式对比:

html
<!-- 方式1:link -->
<link rel="stylesheet" href="styles.css">
css
/* 方式2:@import */
@import url('styles.css');

性能对比表:

对比项link@import
加载时机✅ 页面加载时同步加载❌ 页面加载完成后才加载
是否阻塞不阻塞(并行加载)阻塞后续样式
JS 控制✅ 可通过 JS 添加/删除❌ 无法控制
性能✅ 更快❌ 更慢
兼容性✅ 所有浏览器⚠️ IE5+
优先级

实际影响:

html
<!-- link:立即加载,页面快速渲染 -->
<link rel="stylesheet" href="styles.css">
css
/* @import:页面先白屏,然后闪烁一下才显示样式(FOUC) */
@import url('styles.css');

FOUC(Flash of Unstyled Content):无样式内容闪烁


📝 要点测验

为什么 script 标签放在 body 底部?

历史原因(传统做法):

html
<!DOCTYPE html>
<html>
<head>
  <!-- ❌ 放 head 里会阻塞页面渲染 -->
  <script src="heavy-library.js"></script>
</head>
<body>
  <h1>这段文字要等很久才能显示</h1>
  
  <!-- ✅ 放 body 底部,HTML 先渲染 -->
  <script src="main.js"></script>
</body>
</html>

现代最佳实践(推荐):

html
<head>
  <!-- ✅ 用 defer,既不阻塞又能放 head -->
  <script src="main.js" defer></script>
</head>

结论:

  • 传统项目:放 </body>
  • 现代项目:用 defer<head>
  • 第三方脚本:用 async
DOMContentLoaded 和 load 事件有什么区别?
javascript
// 事件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 对应:

javascript
// jQuery 的 ready 就是 DOMContentLoaded
$(document).ready(function() {
  // DOM ready
});

// 简写
$(function() {
  // DOM ready
});

五、HTML 元素分类

知识点讲解

元素的显示类型

HTML 元素根据 display 属性分为三大类:


块级元素(Block)

特点:

  • 独占一行
  • 宽度默认撑满父容器(width: 100%
  • 可以设置 widthheightmarginpadding
  • 可以包含块级元素和行内元素

常见块级元素:

html
<div>      <!-- 通用容器 -->
<p>        <!-- 段落 -->
<h1>-<h6>  <!-- 标题 -->
<ul> <ol>  <!-- 列表 -->
<li>       <!-- 列表项 -->
<header>   <!-- 页头 -->
<nav>      <!-- 导航 -->
<section>  <!-- 区块 -->
<article>  <!-- 文章 -->
<footer>   <!-- 页脚 -->
<form>     <!-- 表单 -->
<table>    <!-- 表格 -->

行内元素(Inline)

特点:

  • 不独占一行,多个元素可以在同一行
  • 宽高由内容决定
  • ❌ 不能设置 widthheight
  • ⚠️ 设置上下 marginpadding 无效(左右有效)
  • 只能包含文本或其他行内元素

常见行内元素:

html
<span>     <!-- 通用行内容器 -->
<a>        <!-- 链接 -->
<strong>   <!-- 加粗强调 -->
<em>       <!-- 斜体强调 -->
<b>        <!-- 粗体 -->
<i>        <!-- 斜体 -->
<code>     <!-- 代码 -->
<label>    <!-- 表单标签 -->
<input>    <!-- 输入框 -->
<img>      <!-- 图片(特殊行内元素,可设置宽高) -->

行内块元素(Inline-Block)

特点:

  • ✅ 不独占一行(像行内元素)
  • ✅ 可以设置宽高(像块级元素)
  • 元素间有空隙(HTML 换行符导致)

示例:

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>
<!-- 三个盒子在同一行,且可以设置宽高 -->

常见行内块元素:

html
<img>      <!-- 图片 -->
<input>    <!-- 输入框 -->
<button>   <!-- 按钮 -->
<select>   <!-- 下拉框 -->

三种类型对比表

特性BlockInlineInline-Block
独占一行
可设置 width/height
上下 margin/padding
左右 margin/padding
默认宽度100%内容决定内容决定
可包含块级元素

相互转换

css
/* 块级 → 行内 */
div {
  display: inline;
}

/* 行内 → 块级 */
span {
  display: block;
}

/* 任意 → 行内块 */
.element {
  display: inline-block;
}

/* 现代布局(推荐) */
.container {
  display: flex; /* 或 grid */
}

📝 要点测验

为什么 img 是行内元素,却可以设置宽高?

特殊的行内元素:

<img><input><button> 等元素被称为替换元素(Replaced Element),它们有特殊的渲染机制:

html
<img src="photo.jpg" width="200" height="150">
<!-- 虽然是 inline,但可以设置宽高 -->

原因:

  • 这些元素的内容不是由 CSS 决定的,而是由外部资源(图片文件、表单控件)决定
  • 浏览器给它们特殊待遇:默认 display: inline,但表现得像 inline-block

验证:

css
img {
  display: inline; /* 默认值 */
  width: 100px;    /* ✅ 有效 */
  height: 100px;   /* ✅ 有效 */
  margin: 20px;    /* ✅ 上下左右都有效 */
}

结论:<img> 虽然默认是 inline,但它是一个"伪装的 inline-block"。

行内元素之间为什么有空隙?如何消除?

问题演示:

html
<span>A</span>
<span>B</span>
<span>C</span>
<!-- A B C 之间会有小空隙 -->

原因:

  • HTML 中的换行符和空格会被浏览器解析为一个空格
  • 行内元素会保留这些空格

解决方案:

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
<html>
  <body>
    <div>
      <p>Hello</p>
    </div>
  </body>
</html>

转换为 DOM 树:

html
 └─ body
     └─ div
         └─ p
             └─ "Hello"

2️⃣ 构建 CSSOM 树

css
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 的元素渲染树中(占据空间,只是不可见)

示例:

html
<div class="visible">可见</div>
<div class="hidden1">display none</div>
<div class="hidden2">visibility hidden</div>
css
.hidden1 { display: none; } /* ❌ 不在渲染树 */
.hidden2 { visibility: hidden; } /* ✅ 在渲染树,占位 */

4️⃣ Layout(布局/回流)

计算每个元素的:

  • 位置(x, y 坐标)
  • 尺寸(width, height)
  • 盒模型(margin, padding, border)

触发回流的操作(性能杀手):

javascript
// ❌ 会触发回流的操作
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(绘制/重绘)

将渲染树的每个节点转换为屏幕上的实际像素

绘制内容:

  • 文字
  • 颜色
  • 图片
  • 边框
  • 阴影

触发重绘的操作(性能影响较小):

javascript
// ✅ 只触发重绘,不触发回流
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.visibility = 'hidden';

回流 vs 重绘

对比项回流(Reflow)重绘(Repaint)
英文名Reflow / LayoutRepaint
触发条件改变元素几何属性改变元素外观属性
影响范围整个文档树仅当前元素
性能影响⚠️ 非常大较小
是否必然重绘✅ 回流必然重绘❌ 重绘不一定回流

触发回流的操作清单

DOM 操作:

javascript
element.appendChild(child);        // 添加元素
element.removeChild(child);        // 删除元素
element.style.display = 'none';    // 显示/隐藏

样式修改:

javascript
element.style.width = '100px';     // 宽高
element.style.padding = '10px';    // 内边距
element.style.margin = '10px';     // 外边距
element.style.border = '1px';      // 边框
element.style.fontSize = '16px';   // 字号(影响高度)

读取布局信息:

javascript
element.offsetWidth / offsetHeight
element.clientWidth / clientHeight
element.scrollWidth / scrollHeight
element.getBoundingClientRect()
window.getComputedStyle(element)

性能优化技巧

1️⃣ 批量修改样式

javascript
// ❌ 错误:每次修改都触发回流
element.style.width = '100px';
element.style.height = '100px';
element.style.padding = '10px';

// ✅ 正确:通过 class 一次性修改
element.className = 'big-box';

2️⃣ 使用文档片段

javascript
// ❌ 错误:每次 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️⃣ 读写分离

javascript
// ❌ 错误:读写交替,触发多次回流
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

css
/* ❌ 会触发回流 */
.move {
  position: absolute;
  left: 100px;
  top: 100px;
}

/* ✅ 只触发合成(Composite),不回流不重绘 */
.move {
  transform: translate(100px, 100px);
}

GPU 加速的属性:

  • transform
  • opacity
  • filter

📝 要点测验

为什么 display: none 和 visibility: hidden 对性能影响不同?

对比:

css
/* 方式1:display: none */
.hidden1 {
  display: none;
}

/* 方式2:visibility: hidden */
.hidden2 {
  visibility: hidden;
}

渲染流程对比:

步骤display: nonevisibility: hidden
DOM 树✅ 存在✅ 存在
渲染树❌ 不存在✅ 存在
Layout❌ 不计算✅ 计算并占位
Paint❌ 不绘制❌ 不绘制
触发回流✅ 会(因为改变了布局)❌ 不会(只是不可见)

性能影响:

javascript
// ⚠️ display 切换会触发回流(昂贵)
element.style.display = 'none';
element.style.display = 'block'; // 重新计算布局

// ✅ visibility 切换只触发重绘(便宜)
element.style.visibility = 'hidden';
element.style.visibility = 'visible'; // 只是重新绘制

使用建议:

  • 频繁切换:用 visibilityopacity
  • 彻底隐藏:用 display: none
  • 动画效果:用 opacity + transform
为什么 transform 比 left/top 性能好?

动画性能对比:

css
/* ❌ 方案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); }
}

渲染成本对比:

属性LayoutPaintComposite性能
left/top✅ 触发✅ 触发✅ 触发❌ 差
transform✅ 触发✅ 好
opacity✅ 触发✅ 好

原理:

transformopacity 会创建新的合成层(Composite Layer),由 GPU 处理,不影响主线程的布局和绘制。

Chrome DevTools 查看:

F12 → More tools → Rendering → Layer borders

可以看到使用 transform 的元素会单独成为一层(橙色边框)。

最佳实践:

  • 动画用 transform + opacity
  • 避免动画 widthheightlefttop
  • 大量元素动画用 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