HTML 编码与实体
一、HTML 实体编码
1.1 什么是 HTML 实体
HTML 实体是表示特殊字符的字符串,以 & 开头,以 ; 结尾:
<!-- 常用实体 -->
< <!-- < -->
> <!-- > -->
& <!-- & -->
" <!-- " -->
' <!-- ' -->
<!-- 不换行空格 -->
1.2 为什么要用 HTML 实体
在 HTML 中,某些字符有特殊含义,不能直接使用:
<!-- ❌ 错误:< 被浏览器解析为标签开始 -->
<p>5 < 10</p>
<!-- ✅ 正确:使用实体 -->
<p>5 < 10</p>
<!-- ❌ 错误:& 可能影响实体解析 -->
<p>Tom & Jerry</p>
<!-- ✅ 正确:& 实体 -->
<p>Tom & Jerry</p>
1.3 数字实体
除了命名实体,还有数字实体:
<!-- 命名实体 -->
< <!-- < -->
<!-- 十进制数字实体 -->
< <!-- < -->
<!-- 十六进制数字实体 -->
< <!-- < -->
1.4 完整实体列表
| 字符 | 命名实体 | 十进制 | 十六进制 |
|---|---|---|---|
| < | < |
< |
< |
| > | > |
> |
> |
| & | & |
& |
& |
| " | " |
" |
" |
| ' | ' |
' |
' |
| 空格 | |
  |
  |
| © | © |
© |
© |
| ® | ® |
® |
® |
| ™ | ™ |
™ |
™ |
二、字符编码历史
2.1 ASCII
ASCII(American Standard Code for Information Interchange)是最早的字符编码:
- 7 位,共 128 个字符
- 包含英文字母、数字、标点符号和控制字符
- 无法表示非英文字符
2.2 ISO-8859-1
ISO-8859-1(Latin-1)扩展了 ASCII:
- 8 位,共 256 个字符
- 支持西欧语言字符
- 仍然无法支持中文等其他语言
2.3 GB2312 和 GBK
中文编码:
- GB2312:简体中文编码,包含约 6700 个汉字
- GBK:扩展 GB2312,支持更多汉字和繁体字
2.4 UTF-16
Unicode 的早期实现:
- 2 字节或 4 字节编码
- 可以表示所有 Unicode 字符
- 不兼容 ASCII
2.5 UTF-8
现代 Web 的标准:
- 1-4 字节变长编码
- 向后兼容 ASCII
- 可以表示所有 Unicode 字符(包括 emoji)
- 是 HTML5 的默认编码
三、charset 声明
3.1 HTML5 的 charset 声明
<head>
<meta charset="UTF-8">
</head>
3.2 历史原因
为什么 charset 必须在 <head> 靠前位置?
早期浏览器在遇到非 ASCII 字符时可能会误判编码。为了让浏览器尽早用正确编码重新解析,HTML 规范要求 charset 必须在前 1024 字节内被解析。
<!-- ❌ 错误:charset 太靠后 -->
<html>
<head>
<title>页面</title>
<meta charset="UTF-8"> <!-- 可能不会被解析 -->
</head>
<!-- ✅ 正确:charset 靠前 -->
<html>
<head>
<meta charset="UTF-8">
<title>页面</title>
</head>
3.3 HTTP 声明 vs meta 声明
服务器也可以通过 HTTP 头声明编码:
Content-Type: text/html; charset=UTF-8
HTTP 声明优先于 meta 声明。如果服务器设置了正确的 HTTP 头,meta 声明会被忽略。
四、BOM 和 UTF-8
4.1 什么是 BOM
BOM(Byte Order Mark)是文件开头的特殊字节序列,用于表示编码方式:
UTF-8: EF BB BF
UTF-16 LE: FF FE
UTF-16 BE: FE FF
4.2 UTF-8 BOM
UTF-8 文件开头的 EF BB BF 三个字节就是 BOM。
// 检测 UTF-8 BOM
function hasUTF8BOM(buffer) {
return buffer[0] === 0xEF &&
buffer[1] === 0xBB &&
buffer[2] === 0xBF;
}
// 移除 BOM
function removeBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
return content.slice(1);
}
return content;
}
4.3 BOM 的问题
UTF-8 BOM 有时会导致问题:
- 在 JSON 中,BOM 开头会导致 JSON.parse 错误
- 在某些服务器配置中,BOM 会影响输出
- 纯 ASCII 文件不需要 BOM
建议:UTF-8 文件不要带 BOM。
五、XSS 和 HTML 编码
5.1 XSS 攻击原理
XSS(Cross-Site Scripting)发生在用户输入被当作 HTML 执行时:
<!-- 恶意输入 -->
<script>alert('XSS')</script>
<!-- 如果直接输出 -->
<p><script>alert('XSS')</script></p>
<!-- 浏览器会执行脚本!-->
5.2 HTML 编码防御
对用户输入进行 HTML 编码可以防止 XSS:
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 使用
const userInput = '<script>alert("XSS")</script>';
element.textContent = escapeHTML(userInput);
// 输出: <script>alert("XSS")</script>
5.3 不同上下文的编码
不同上下文需要不同的编码方式:
// HTML 上下文编码
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// JavaScript 上下文编码(用于事件处理器)
function escapeJS(str) {
return str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/'/g, "\\'")
.replace(/</g, '\\x3C')
.replace(/>/g, '\\x3E');
}
// URL 上下文编码
function escapeURL(str) {
return encodeURIComponent(str)
.replace(/'/g, '%27');
}
六、Unicode emoji 在 HTML 中的处理
6.1 emoji 的编码
emoji 是 Unicode 字符,通常使用 UTF-8 编码:
😀 (Grinning Face) = F0 9F 98 80 (4 字节 UTF-8)
6.2 emoji 显示问题
有时 emoji 在某些设备或浏览器上无法正确显示:
<!-- 使用图片代替 emoji -->
<img src="emoji/smile.png" alt="😀">
<!-- 使用 CSS 控制 emoji 显示 -->
<span class="emoji">😀</span>
<style>
.emoji {
font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
</style>
6.3 零宽连接符
某些 emoji 组合需要零宽连接符:
<!-- 人物 + 肤色 -->
<span>👱🏻‍♂️</span>
<!-- 1F471 = person -->
<!-- 1F3FB = light skin tone -->
<!-- 200D = zero width joiner -->
<!-- 2642 = male sign -->
<!-- FE0F = variation selector -->