HTML 字符实体与编码

小飞兽 HTML/CSS 306 次阅读 2026-05-31

一、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
    • 结尾分号;(分号,通常不可省略)

    实体编号又分为数字序号(&#60;)和十六进制序号(&#x3C;)两种形式。

    2.2 常用 HTML 字符实体对照表

    显示结果描述实体名称实体编号
    <小于号&lt;&#60;
    >大于号&gt;&#62;
    &和号&amp;&#38;
    "双引号&quot;&#34;
    '单引号&apos;&#39;
     空格(不换行空格)&nbsp;&#160;
    ©版权符号&copy;&#169;
    ®注册商标&reg;&#174;
    商标&trade;&#8482;
    ×乘号&times;&#215;
    ÷除号&divide;&#247;
    &和号&amp;&#38;
    欧元符号&euro;&#8364;
    £英镑符号&pound;&#163;
    ¥人民币/日元符号&yen;&#165;
    ¢美分符号&cent;&#162;
    ½二分之一&frac12;&#189;
    ¼四分之一&frac14;&#188;
    ¾四分之三&frac34;&#190;
    省略号&hellip;&#8230;
    左双引号&ldquo;&#8220;
    右双引号&rdquo;&#8221;
    左单引号&lsquo;&#8216;
    右单引号(缩写词撇号)&rsquo;&#8217;
    &和号&amp;&#38;
    §章节符号&sect;&#167;
    段落符号&para;&#182;
    圆点符号&bull;&#8226;
    °度符号&deg;&#176;
    ±加减号&plusmn;&#177;
    ²上标2(平方)&sup2;&#178;
    ³上标3(立方)&sup3;&#179;
    µ微符号&micro;&#181;
    ¸软音符(CEDILLA)&cedil;&#184;
    «左双尖括号&laquo;&#171;
    »右双尖括号&raquo;&#187;
    ¬否定符号&not;&#172;
    ­软换行符(选择性连字符)&shy;&#173;

    2.3 数字实体 vs 命名实体

    命名实体(如 &lt;)直观易懂,但需要浏览器内置的实体映射表支持。所有字符也可以使用数字形式表示:

    • 十进制&#60;<
    • 十六进制&#x3C;<

    十六进制形式以 &#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='他说:&quot;你好&quot;'>  <!-- 需要转义双引号 -->
    

    三、代码示例

    示例一:基础字符实体使用

    <!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; &mdash; <mark>小于号</mark> 和 <mark>大于号</mark> 必须转义</p>
    <p>版权声明:&copy; 2026 示例公司 &reg; 注册商标&mdash;<mark>版权和注册符号</mark></p>
    <p>价格:&yen;99.00 &divide; 3 = &yen;33.00&mdash;<mark>人民币和除号</mark></p>
    <p>分数:&frac14; + &frac12; = &frac34;&mdash;<mark>分数实体</mark></p>
    <p>引用:&ldquo;道可道,非常道。&rdquo;&mdash;<mark>中文引号</mark></p>
    <p>省略:&hellip;&mdash;<mark>省略号</mark></p>
    </div>

    <h3>源代码(转义前)</h3>
    <pre><code>&lt;p&gt;1 + 2 &lt; 5 &amp; 的结果是 &amp;true;&lt;/p&gt;
    &lt;p&gt;版权声明:&copy; 2026 公司 &reg;&lt;/p&gt;
    &lt;p&gt;价格:&yen;99.00 &divide; 3 = &yen;33.00&lt;/p&gt;</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 攻击
  • 将 &lt; &gt; &amp; " ' 转义为 HTML 实体
*/ function escapeHtml(str) { const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }; return str.replace(/[&<>"']/g, char => map[char]); }

/**

  • 反转义函数

*/
function unescapeHtml(str) {
const map = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
'&#x27;': "'",
'&#x2F;': '/'
};
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);
// &lt;script&gt;alert(&quot;XSS!&quot;)&lt;/script&gt;

// 在页面中渲染
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');
  // 输出: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;这是一段用户评论

// 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 特殊字符均被替换为实体形式:<&lt;>&gt;"&quot;&&amp;'&#39;。转义后的内容通过 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:&nbsp; 和普通空格有什么区别?

普通空格是 可断行空格,浏览器在渲染时会在适当位置自动换行。&nbsp;(不换行空格,Unicode U+00A0)禁止在当前位置换行——浏览器既不会将其视为单词间的可断行点,也不会合并多个连续的 &nbsp;。常见用途:保持英文单词不被错误断行(如 "10 km")、在列表中强制对齐多行内容。需要注意的是,&nbsp; 不是用来增加文字间距的,滥用会导致屏幕阅读器错误朗读、可访问性问题以及搜索引擎对内容的误读。

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:htmlspecialcharshtmlentities 有什么区别?

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 入门,包含完整的实体列表和使用指南。
    • 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 安全知识体系。