HTML 字符实体与编码
一、Introduction 简介
在 HTML 文档中,某些字符是 HTML 语言本身的保留字或特殊符号,例如小于号(<)、大于号(>)、双引号(")以及和号(&)等。如果直接在 HTML 中书写这些字符,浏览器会将其误解为 HTML 标签或属性语法的一部分,从而导致页面渲染异常、布局错乱,甚至引发安全隐患(如 XSS 跨站脚本攻击)。
为了解决这一问题,HTML 规范引入了字符实体(Character Entity)这一机制。字符实体是一段以 & 开头、以 ; 分隔的字符串,用于表示那些在 HTML 中有特殊含义或不便直接书写的字符。此外,当网页需要在 URL、HTTP 请求头、XML 文档等上下文中传输时,还需要用到URL 编码(Percent-Encoding)和HTML 数值编码等更广泛的字符编码方案。
理解和正确使用字符实体与编码,是编写健壮、安全、符合规范的 HTML 代码的基础。无论是防止 XSS 攻击、确保多语言内容正确显示,还是在后端与前端之间正确传递含特殊字符的数据,字符编码知识都是不可或缺的。
二、基础语法
2.1 HTML 字符实体三要素
每个 HTML 字符实体由三部分组成:
- 开头标识:
&(ampersand) - 实体名称或编号:实体名称(如
lt)或实体编号(如#60) - 结尾分号:
;(分号,通常不可省略) - 十进制:
<→< - 十六进制:
<→<
实体编号又分为数字序号(<)和十六进制序号(<)两种形式。
2.2 常用 HTML 字符实体对照表
| 显示结果 | 描述 | 实体名称 | 实体编号 |
|---|---|---|---|
| < | 小于号 | < | < |
| > | 大于号 | > | > |
| & | 和号 | & | & |
| " | 双引号 | " | " |
| ' | 单引号 | ' | ' |
| 空格(不换行空格) | |   | |
| © | 版权符号 | © | © |
| ® | 注册商标 | ® | ® |
| ™ | 商标 | ™ | ™ |
| × | 乘号 | × | × |
| ÷ | 除号 | ÷ | ÷ |
| & | 和号 | & | & |
| € | 欧元符号 | € | € |
| £ | 英镑符号 | £ | £ |
| ¥ | 人民币/日元符号 | ¥ | ¥ |
| ¢ | 美分符号 | ¢ | ¢ |
| ½ | 二分之一 | ½ | ½ |
| ¼ | 四分之一 | ¼ | ¼ |
| ¾ | 四分之三 | ¾ | ¾ |
| … | 省略号 | … | … |
| “ | 左双引号 | “ | “ |
| ” | 右双引号 | ” | ” |
| ‘ | 左单引号 | ‘ | ‘ |
| ’ | 右单引号(缩写词撇号) | ’ | ’ |
| & | 和号 | & | & |
| § | 章节符号 | § | § |
| ¶ | 段落符号 | ¶ | ¶ |
| • | 圆点符号 | • | • |
| ° | 度符号 | ° | ° |
| ± | 加减号 | ± | ± |
| ² | 上标2(平方) | ² | ² |
| ³ | 上标3(立方) | ³ | ³ |
| µ | 微符号 | µ | µ |
| ¸ | 软音符(CEDILLA) | ¸ | ¸ |
| « | 左双尖括号 | « | « |
| » | 右双尖括号 | » | » |
| ¬ | 否定符号 | ¬ | ¬ |
| | 软换行符(选择性连字符) | ­ | ­ |
2.3 数字实体 vs 命名实体
命名实体(如 <)直观易懂,但需要浏览器内置的实体映射表支持。所有字符也可以使用数字形式表示:
十六进制形式以 &#x 开头,便于与编程语言中的十六进制表示法对应。数字形式的优势在于:几乎所有字符(包括罕见 Unicode 字符)都可以用数字实体表示,而不依赖命名实体库。
2.4 URL 编码(Percent-Encoding)
当字符需要出现在 URL 中(如查询字符串)时,需要遵循 RFC 3986 规范进行百分号编码。规则是:每个字节表示为 %XX(XX 为该字节的十六进制值)。非 ASCII 字符通常先 UTF-8 编码为字节序列,再逐字节编码。
空格 → %20
中文 "你好" → %E4%BD%A0%E5%A5%BD
"&" → %26
"=" → %3D
"+" → %2B(表单提交时空格会编码为 +)
2.5 JavaScript 中的编码函数
encodeURIComponent(str):对字符串进行完整的 URL 参数编码(不会编码 A-Z a-z 0-9 - _ . ! ~ * ' ( ))
encodeURI(str):对字符串进行 URI 级别的编码(保留大部分字符,只编码特殊字符)escape(str)/unescape(str):已废弃,不推荐使用
decodeURIComponent(str)/decodeURI(str):对应的解码函数
2.6 HTML 属性中的引号处理
HTML 属性值必须以单引号或双引号包裹。如果属性值本身含有引号,需要使用对应的字符实体:双引号属性中书写单引号不需要转义,反之亦然。混用时则需要转义。
<input value="他说:'你好'"> <!-- 无需实体 -->
<input value='他说:"你好"'> <!-- 需要转义双引号 -->
三、代码示例
示例一:基础字符实体使用
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>HTML 字符实体示例</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.demo-box {
background: #f5f5f5; padding: 16px;
border-radius: 8px; margin: 12px 0;
font-size: 18px; line-height: 2;
}
code { background: #e8e8e8; padding: 2px 6px; border-radius: 4px; }
</style>
</head>
<body>
<h2>HTML 字符实体基础演示</h2>
<div class="demo-box">
<p>1 + 2 < 5 的结果是 &true; — <mark>小于号</mark> 和 <mark>大于号</mark> 必须转义</p>
<p>版权声明:© 2026 示例公司 ® 注册商标—<mark>版权和注册符号</mark></p>
<p>价格:¥99.00 ÷ 3 = ¥33.00—<mark>人民币和除号</mark></p>
<p>分数:¼ + ½ = ¾—<mark>分数实体</mark></p>
<p>引用:“道可道,非常道。”—<mark>中文引号</mark></p>
<p>省略:…—<mark>省略号</mark></p>
</div>
<h3>源代码(转义前)</h3>
<pre><code><p>1 + 2 < 5 & 的结果是 &true;</p>
<p>版权声明:© 2026 公司 ®</p>
<p>价格:¥99.00 ÷ 3 = ¥33.00</p></code></pre>
</body>
</html>
示例二:JavaScript 中的编解码
<script>
// 基础编解码演示
const raw = 'Hello 世界! <3 > 2? "quote" &';
console.log('原始字符串:', raw);
const encoded = encodeURIComponent(raw);
console.log('encodeURIComponent:', encoded);
// 输出: Hello%20%E4%B8%96%E7%95%8C!%20%3C3%20%3E%202%3F%20%22quote%22%20%26
const decoded = decodeURIComponent(encoded);
console.log('decodeURIComponent:', decoded);
// 输出: Hello 世界! <3 > 2? "quote" &
// URL 路径 vs 查询参数编码的区别
const path = '/users/张三';
const query = 'name=张三&age=30';
console.log('encodeURI(保留URI特殊字符):', encodeURI(path));
// /users/张三
console.log('encodeURIComponent(完全编码):', encodeURIComponent(path));
// %2Fusers%2F%E5%BC%A0%E4%B8%89
// PHP 后端对应的编解码
// urlencode() <=> encodeURIComponent()
// rawurlencode() <=> encodeURI()(将空格编码为 %20)
// htmlspecialchars() <=> HTML 字符实体转义
// htmlentities() <=> 所有非 ASCII 字符转实体
// 完整示例:构建带特殊字符的 URL
const params = new URLSearchParams();
params.append('q', 'HTML & CSS <JavaScript>');
params.append('tag', '"前端"');
console.log('URLSearchParams:', params.toString());
// q=HTML+%26+CSS+%3CJavaScript%3E&tag=%22%E5%89%8D%E7%AB%AF%22
</script>
示例三:防止 XSS 的实体转义函数
<script>
/**
HTML 实体转义函数 - 防止 XSS 攻击
- 将 < > & " ' 转义为 HTML 实体
*/
function escapeHtml(str) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return str.replace(/[&<>"']/g, char => map[char]);
}
/**
- 反转义函数
*/
function unescapeHtml(str) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
''': "'",
''': "'",
'/': '/'
};
return str.replace(/&(amp|lt|gt|quot|#39|#x27|#x2F);/g,
match => map[match] || match);
}
// 测试
const userInput = '<script>alert("XSS!")</script>';
const safe = escapeHtml(userInput);
console.log('原始输入:', userInput);
console.log('转义后:', safe);
// <script>alert("XSS!")</script>
// 在页面中渲染
document.getElementById('output').textContent = safe;
// textContent 会自动处理实体,无需手动转义
// 但 innerHTML 必须使用转义后的内容
document.getElementById('unsafe').innerHTML = safe;
</script>
<!-- HTML 使用场景 -->
<div id="output"></div>
<div id="unsafe"></div>
示例四:后端 PHP 中的转义处理
<?php
// HTML 输出转义 - 显示用户内容前必须转义
$userComment = '<script>alert("XSS")</script>这是一段用户评论';
echo htmlspecialchars($userComment, ENT_QUOTES, 'UTF-8');
// 输出: <script>alert("XSS")</script>这是一段用户评论
// ENT_QUOTES 同时转义单引号和双引号
// ENT_HTML5 支持 HTML5 文档类型
// 数据库查询转义 - 使用 PDO 预处理语句
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute([':name' => $userInput]);
// 预处理语句自动处理 SQL 转义,无需手动 addslashes
// URL 参数编解码
$raw = '你好世界';
$encoded = rawurlencode($raw); // %E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C
$decoded = rawurldecode($encoded); // 你好世界
// JSON 中的中文和特殊字符
$data = ['msg' => 'Hello <World>', 'name' => '张三'];
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// {"msg":"Hello <World>","name":"张三"}
<?>
四、运行效果
示例一运行效果
页面加载后,在灰色演示框内可以看到:小于号和大于号正确显示为数学比较符号而非 HTML 标签;版权符号 © 和注册符号 ® 正常渲染;人民币符号 ¥ 和除号 ÷ 以数学符号形式呈现;分数 ¼、½、¾ 以标准印刷体分数显示;中文引号 "" 和省略号 … 同样以正确样式渲染。如果在 HTML 中直接写原始字符而未经转义,浏览器很可能将 <script> 标签当作真实脚本执行或直接忽略该标签,导致内容不完整或安全风险。
示例二运行效果
JavaScript 控制台输出展示:原始字符串 Hello 世界! <3 > 2? "quote" & 经过 encodeURIComponent 后,所有非安全字符均被转换为百分号编码形式(中文每个汉字编码为三个百分号字节);经过解码后完整恢复为原始字符串。URLSearchParams 自动处理键值对的编码,输出的查询字符串可以直接安全地附加在 URL 后面使用。
示例三运行效果
XSS 演示中,带有 <script> 标签的用户输入经过 escapeHtml 函数转义后,所有 HTML 特殊字符均被替换为实体形式:< → <、> → >、" → "、& → &、' → '。转义后的内容通过 innerHTML 渲染时,浏览器将其视为纯文本而非可执行脚本,从而有效防止 XSS 攻击。
示例四运行效果
PHP 中的 htmlspecialchars($str, ENT_QUOTES, 'UTF-8') 对用户评论进行转义后,包含 <script> 标签的输入在页面中以纯文本形式显示,不会触发脚本执行。PDO 预处理语句确保数据库查询中的特殊字符不会造成 SQL 注入漏洞。json_encode($data, JSON_UNESCAPED_UNICODE) 在输出 JSON 时保留中文原文,无需额外的 Unicode 转义。
五、常见问题
Q1:什么时候必须使用字符实体?
以下三种情况必须使用字符实体:①在 HTML 内容中书写 <、>、& 这三个特殊符号时,它们是 HTML 语法的一部分,必须转义;②在 HTML 属性值中书写双引号或单引号时(取决于属性是用哪种引号包裹的);③需要显示键盘上难以直接输入的符号时(如版权 ©、注册 ®、商标 ™、欧元 € 等),使用命名实体比记忆数字编号更高效。
Q2: 和普通空格有什么区别?
普通空格是 可断行空格,浏览器在渲染时会在适当位置自动换行。 (不换行空格,Unicode U+00A0)禁止在当前位置换行——浏览器既不会将其视为单词间的可断行点,也不会合并多个连续的 。常见用途:保持英文单词不被错误断行(如 "10 km")、在列表中强制对齐多行内容。需要注意的是, 不是用来增加文字间距的,滥用会导致屏幕阅读器错误朗读、可访问性问题以及搜索引擎对内容的误读。
Q3:UTF-8 编码下还需要转义中文吗?
现代网页统一使用 UTF-8 编码,中文字符在 HTML 文件中可以直接书写,无需转义为实体。实体编码的主要目的是处理 HTML 语法中的特殊字符(<、>、& 等),而非语言字符本身。不过在某些特殊上下文中(如 JSON 字符串内的 HTML 内容、URL 查询参数、XML 文档属性值),中文仍然需要按相应规范进行编码(如 URL 编码为 %E4%BD%A0%E5%A5%BD)。
Q4:XSS 攻击和字符实体有什么关系?
XSS(Cross-Site Scripting,跨站脚本攻击)的核心原理是:攻击者将恶意 JavaScript 代码注入网页,当其他用户访问该页面时,恶意代码在用户浏览器中执行,从而窃取 Cookie、劫持会话、执行恶意操作。如果网页在展示用户提交的内容时,直接使用 innerHTML 或未经转义地插入 HTML,攻击者即可植入 <script> 标签或事件处理器(如 <img src=x onerror=...>)。防御 XSS 的最基本手段就是输出转义:将用户输入中的 <、>、&、"、' 转义为 HTML 实体,使浏览器将其视为纯文本而非可执行代码。
Q5:htmlspecialchars 和 htmlentities 有什么区别?
htmlspecialchars 只转义五个最有安全风险的字符:&、<、>、"、'。它保留了可读性,适合大多数 Web 输出的转义场景。htmlentities 则尝试将所有有对应 HTML 实体的字符都进行转义(包括重音字母、法语德语特殊字符等)。性能上 htmlspecialchars 更快,因为它只需处理有限的字符集。安全转义输出时,优先使用 htmlspecialchars。
Q6:URL 编码中空格是 %20 还是 +?
两者均可表示空格,但适用场景不同:%20 是 RFC 3986 标准的通用 URL 编码形式,适用于 URL 路径和查询参数的每一个字节。+ 是 application/x-www-form-urlencoded 格式(传统 HTML 表单 POST 提交)的历史遗留编码方式,仅适用于查询字符串部分。JavaScript 的 encodeURIComponent() 使用 %20,而 PHP 的 urlencode() 使用 +。最佳实践:查询参数使用 URLSearchParams(自动处理所有边界情况),URL 路径使用 encodeURI。
六、延伸阅读
- MDN - HTML 字符实体:MDN HTML 入门,包含完整的实体列表和使用指南。
- OWASP XSS 防护手册:OWASP XSS Prevention Cheat Sheet,权威的跨站脚本攻击防御规范,详细说明了输出转义的规则矩阵(Rule #0 到 Rule #5)。
- RFC 3986 - URL 编码标准:https://datatracker.ietf.org/doc/html/rfc3986,URL 百分号编码的原始规范文档。
- PHP htmlspecialchars 文档:PHP 官方文档,详细说明各个转义标志位(ENT_QUOTES、ENT_HTML5 等)的含义和用法。
- Unicode 字符百科:https://unicode-table.com/,可查询每个 Unicode 字符的十进制、十六进制实体编号以及所属语言脚本。
- 前端安全:CSRF 与 XSS:现代前端工程化中,字符转义是防御 XSS 的第一道防线,但完整的 XSS 防护还需要配合 CSP(Content Security Policy)、HTTP Only Cookie、SameSite Cookie 等多层防御策略一同实施。
HTML 字符实体与编码是 Web 开发中最基础也最容易被忽视的安全知识点之一。理解 & < > " ' 这五个核心转义对象的含义,熟练使用 htmlspecialchars 进行输出转义,正确使用 encodeURIComponent 处理 URL 参数,就能在大多数场景下避免 XSS 攻击和数据错乱问题。在此基础上,进一步了解 UTF-8 编码原理、Unicode 码点和 CSP 策略,就能构建起完整的 Web 安全知识体系。