写网页时你迟早会遇到这样的困惑:想在页面上显示一个小于号,结果它要么消失,要么把后面的内容搞乱了。问题的根源在于,某些字符对 HTML 来说身兼两职——它们既是构成标记的语法,又可能是你想原样展示给用户的内容。HTML 实体正是为化解这种身份冲突而生的。理解它们存在的理由,许多关于网页显示和安全的疑惑都会迎刃而解。

有些字符身兼两职

HTML 用一些特定字符来表达结构。尖括号标记标签的开始和结束,和号引出特殊序列,引号用来包裹属性值。问题在于,这些字符同时也是人们日常文字里会用到的普通符号。当浏览器看到一个小于号时,它无法凭空知道你是想开始一个标签,还是只想显示这个符号本身。

这种歧义如果不解决,网页就无法可靠地同时承载"标记"和"内容"。实体就是用来明确告诉浏览器:"这里我要的是这个字符本身,而不是它的语法含义"。

实体的基本形式

HTML 实体是一种特殊的写法,用来代表某个字符。它通常以和号开始、以分号结束,中间是一个名字或一个数字编码。比如要显示一个小于号,就写成对应的实体,浏览器看到后会把它渲染成那个符号,而不会把它当作标签的开始。

实体既有便于记忆的命名形式,也有基于字符编号的数字形式。无论哪种,目的都一样:把一个有语法含义的字符,安全地表达成"我只想显示它"。

解决歧义,让内容如实呈现

实体最直接的作用,就是消除前面说的歧义。当你需要在页面上展示一个本身是 HTML 语法的字符时,用它的实体形式,浏览器就会老老实实地把它显示出来,而不会误解成标记。这样,你写的内容才能如实地呈现给用户。

这在展示代码片段、数学符号或任何含特殊字符的文本时尤其重要。没有实体,这些内容就会被浏览器误读,导致显示错乱甚至页面结构被破坏。

它也是安全的第一道防线

实体的意义远不止于"正确显示"。当网页要展示来自用户的内容时,如果不对其中的特殊字符做实体化处理,攻击者就可能在输入里塞进恶意标记,让浏览器把它当作真正的 HTML 甚至脚本来执行。这正是跨站脚本攻击的核心机制。

把用户内容里的危险字符转成实体,浏览器就会把它们当作纯文本显示,而不是当作可执行的标记。于是一段试图注入脚本的输入,就只会原样显示成无害的文字。实体化因此成为防范这类攻击的基础手段。

不只是尖括号

需要转义的字符不止尖括号。和号本身也需要,否则它可能被误认为是另一个实体的开始。在属性值里,引号同样关键——一个未转义的引号可能提前结束属性,从而打破标签结构,甚至被利用来注入额外属性。

究竟哪些字符必须转义,取决于你把内容放在哪里。这也引出一个重要观念:转义不是一刀切,而要看具体的上下文。放在标签之间的文本和放在属性值里的文本,需要转义的字符并不完全相同。

编码与解码是一对

实体化是把字符转成实体,反过来,浏览器在渲染时会把实体解码回原始字符显示给用户。这一对操作必须配合得当:内容在写入 HTML 前应当被正确编码,而不应在错误的环节被重复编码或提前解码。

处理不当会闹出笑话:用户输入的内容被编码了不止一次,于是页面上直接显示出实体本身的字符序列,而不是它代表的符号。理解编码和解码各自该在哪一步发生,才能让内容既安全又正确地呈现。

一个小机制,两重价值

归根结底,HTML 实体是一个看似琐碎、实则关键的机制。它一面解决了"语法字符如何当作内容显示"的歧义,让网页能如实呈现任意文本;一面又构成了抵御注入攻击的第一道防线,让用户内容无法越界变成可执行的标记。

下次当你看到一段实体写法时,不妨记住它在默默承担的两份职责:让内容正确,也让页面安全。把它用对,许多显示错乱和安全隐患都能在源头被化解。