vlambda博客
学习文章列表

正确处理 HTML 5 标签的关闭

背景


在将公司一款使用 jQuery 的产品升级到最新的 jQuery 3.6 之后发现部分页面出现了排版异常的问题,查阅了通过 jQuery 生成的 HTML 和 jQuery 3.5 升级文档(https://jquery.com/upgrade-guide/3.5/) 后发现是因为 jQuery 从 3.5 版本开始区分 HTML 模式(HTML mode)和 XHTML 模式(XHTML mode) 来解析 HTML 了。

假设有这样一段 js 代码用于创建 jQuery 对象:

jQuery("<div/><span/>")

我们期望生成的 HTML 是这样的:

<div></div>
<span></span>

在 jQuery 3.5 以前生成的确实也是上述内容,但 3.5 版本开始,除非当前为 XHTML 模式,否则 jQuery 会生成这样的 HTML:

<div>
<span></span>
</div>

由于生成的 HTML 发生了变化,从而导致页面排版出现问题。

那么 jQuery 为什么要做这样的变化呢,jQuery 3.5 升级文档中也有解释:

Modern browsers usually parse and render HTML strings in HTML mode. Elements such as <p> and <span> are never self-closing in HTML, so the browser interprets a string like <p/><span/> as <p><span> and leaves the tags open. The browser closes the tags at the end of the string or at an element that cannot be contained in the element, so another <p> tag closes an existing open <p> tag.

现代浏览器通常以 HTML 模式解析和呈现 HTML 字符串。诸如 <p> 和 <span> 之类的元素在 HTML 中永远不会自动关闭,因此浏览器将诸如<p/><span/> 之类的字符串解析为 <p><span> 并使标记保持打开状态。浏览器将关闭字符串末尾或元素中不能包含其它元素的标签,因此另一个 <p> 标签将关闭现有的打开的 <p> 标签。

—  jQuery Core 3.5 Upgrade Guide

虽然通过这个解释已经基本明白是什么原因了,但也想正好借此机会来看看 HTML 规范中有关标签关闭(闭合)是怎么规定的。


正确处理 HTML 5 标签的关闭
XHTML



记得我第一次系统性的学习 HTML 是从学校图书馆借到了一本名为《HTML与CSS网站设计实践之旅》(https://book.douban.com/subject/3121016/)的书开始的,这是一本非常好的入门书,结构清晰上手简单,感谢这本书奠定了我的 HTML 基础。不过书名虽然是《HTML与CSS网站设计实践之旅》,但实际上书中介绍的其实是 XHTML 的知识。

我们知道,现在最新的 HTML 规范是 HTML 5,在此之前还有 HTML 4.01、XHTML 1.1、XHTML 1.0 等规范。现在的 HTML 5 一看就和 HTML 4.01 一脉相承,都是 HTML 开头的规范,而 XHTML 1.1 则在 HTML 前多了个 X,好像有点区别。

XHTML 是 eXtensible HyperText Markup Language(可扩展超文本标记语言)的缩写。由于 HTML 相当的宽松,导致容易编写出格式错误的代码,不同浏览器也可能解析出不同的结果,因此 W3C 在 HTML 4 的基础上制定了 XHTML 1.0 规范,目的是使用格式良好的 XML 来改进代码的编写和浏览器解析的问题。


正确处理 HTML 5 标签的关闭
格式良好



因为 XHTML 必须是格式良好(Well-formed(https://www.w3.org/TR/xhtml1/#wellformed)的,所以书写规范和 XML 一样,例如标签必须成对出现,不能只有开始标签而没有结束标签。

这是一段来自 HTML 4.01 规范中介绍元素结束标记可以忽略(https://www.w3.org/TR/html401/intro/sgmltut.html#h-3.2.1)的代码,这样的代码是合法的:

<DIV>
<P>This is the paragraph.
</DIV>

但在 XHTML 中指出:

For non-empty elements, end tags are required.

对于非空元素而言,结束标签也是必须的。

—  XHTML™ 1.0 The Extensible HyperText Markup Language (Second Edition)

因此在 XHTML 中的正确的写法必须是:

<p>here is a paragraph.</p><p>here is another paragraph.</p>

正确处理 HTML 5 标签的关闭

可以看到,HTML 4.01 允许的这种写法用在 XHTML 中,浏览器会显示错误提示。

不过这个“空元素(Empty Elements)”是什么呢?


正确处理 HTML 5 标签的关闭
空元素


在 XHTML 规范中没找到相关的定义,但因为 XHTML 是在 HTML 4.01 的基础上制定的,于是在 HTML 4.01 中找到了它:

Some HTML element types have no content. For example, the line break element BR has no content; its only role is to terminate a line of text. Such empty elements never have end tags.

一些 HTML 元素类型没有内容。例如,换行元素 BR 不包含任何内容;它的唯一作用是终止一行文本。这样的元素永远不会带有结束标签。

—  HTML 4.01 Specification

也就是说,空元素(Empty Elements)指的是诸如 <br /><hr /><img /> 这样不包含任何内容的元素。

在 HTML 4 中,这些空元素不但可以写成不带结束标签的形式,甚至都不用关闭标签,也就是说写成 <br><hr><img> 都是合法的。

但在 XHTML 又因为前面提到的格式良好的要求,规定了:

Empty elements must either have an end tag or the start tag must end with />. For instance, <br/> or <hr></hr>.

空元素必须带有结束标签,或者开始标签必须以 /> 结尾。例如,<br/> 或 <hr></hr>

—  XHTML™ 1.0 The Extensible HyperText Markup Language (Second Edition)

也就是说 XHTML 因为承袭了 XML 格式规范的优良传统,标签要么成对出现 <hr></hr>,要么写成 <br/> 直接闭合的形式。

当然,XHTML 的制定者也考虑到了,非空元素不应该写为最小化(minimized)的形式:

Given an empty instance of an element whose content model is not EMPTY (for example, an empty title or paragraph) do not use the minimized form (e.g. use <p> </p> and not <p />).

当给定的元素内容为空,但其不是一个可空元素时(例如:空的标题或段落),请不要使用最小化形式(例如,使用 <p> </p> 而不是 <p />)。

—  XHTML™ 1.0 The Extensible HyperText Markup Language (Second Edition)

正确处理 HTML 5 标签的关闭

但我认为这应该是语义上的,因为 <p /> 实际上是符合 XML 的规范的,即使这么写浏览器也不会报错,浏览器还是会将它解析为 <p></p>

总之,因为 XHTML 为了规范代码的书写,避免不同浏览器解析出不同内容,引入了 XML 的编写习惯,空元素必须关闭。


正确处理 HTML 5 标签的关闭
HTML 5


很多资料上在介绍 HTML 5 时都会提及一句,与 HTML 5 竞争的 XHTML 2 规范止于草案,未能成为正式规范的故事,一则漫画很好的解释了 XHTML、XHTML 2 和 HTML5 的关系:混乱的标记语言XHTML2/HTML5(Misunderstanding Markup: XHTML 2/HTML 5 Comic Strip)中文翻译版(https://www.cnblogs.com/JustinYoung/archive/2009/07/30/misunderstanding-markup-xhtml-2-comic-strip.html) | 英文原版(http://www.smashingmagazine.com/2009/07/29/misunderstanding-markup-xhtml-2-comic-strip/)

话题回到标签闭合上,HTML 5 是这样规定开始标签的:

if the element is one of the void elements, or if the element is a foreign element, then there may be a single U+002F SOLIDUS character (/). This character has no effect on void elements, but on foreign elements it marks the start tag as self-closing.

如果该元素是void 元素或foreign 元素,则可能存在单个 U+002F SOLIDUS 字符(/)。该字符对void 元素没有影响,但会将foreign 元素的开始标签设为自动关闭。

—  HTML 5.2

也就是说将开始标签写成自闭合的形式,对 void 元素没有影响,会当成没有 / 来解析。那 void 元素是什么呢?


正确处理 HTML 5 标签的关闭
Void 元素


HTML 5 规范将 HTML 元素(https://www.w3.org/TR/html52/syntax.html#writing-html-documents-elements)分为6大类,其中就规定了哪些元素属于void 元素(https://www.w3.org/TR/html52/syntax.html#void-elements)

<area>, <base>, <br>, <col>, <embed>, <hr>, <img>, <input>, <link>, <meta>, <param>, <source>, <track>, <wbr>

这不就是 XHTML 1 和 HTML 4 的空元素嘛。

可以对比一下 void 元素之一的 br 元素(https://www.w3.org/TR/html52/textlevel-semantics.html#elementdef-br) 与常规元素之一的 p 元素(https://www.w3.org/TR/html52/grouping-content.html#the-p-element) 在 HTML 5 规范中“省略标签”(tag omission)部分的描述:

  • The br element

    • No end tag

    • (没有结束标签)

  • The p element

    • <p> element’s end tag may be omitted if the <p> element is immediately followed by an <address><article><aside><blockquote><details><div><dl><fieldset><figcaption><figure><footer><form><h1><h2><h3><h4><h5><h6><header><hr><main><nav><ol><p><pre><section><table>, or <ul>, element, or if there is no more content in the parent element and the parent element is an HTML element that is not an <a><audio><del><ins><map><noscript>, or <video> element.

    • (如果 <p> 元素后紧跟 <address><article><aside><blockquote><details><div><dl><fieldset><figcaption><figure><footer><form><h1><h2><h3><h4><h5><h6><header><hr><main><nav><ol><p><pre><section><table> 或 <ul> 元素;或者父元素中没有更多的内容,并且父元素是 <a><audio><del><ins><map><noscript> 和 <video> 以外的元素。)

可见 HTML 5 中,<br> 只需要写成 <br> 不需要结束标签,而增加了 / 写为 <br /> 自闭合的形式也没有任何影响,仍然按 <br> 解析;而 <p> 的结束标签在一些情况下是可以省略的。

正确处理 HTML 5 标签的关闭

这点在现代浏览器中也可以得到印证:

  • 在段落 p1 中,本来没有关闭该段落,但因为紧跟了一个 <div> 标签,所以浏览器自动生成了一个 </p> 来关闭该段落

  • 在段落 p2 中,自闭合形式书写的 <br /> 被解析为了 <br>

那么再回到本文最开始的例子:

<div/><span/>

现代浏览器会直接忽略 HTML 5 中的自闭合写法,将其解析为 <div><span>,但这两个元素都不是 void 元素。

并且在 HTML 5 规范中,div(https://www.w3.org/TR/html52/grouping-content.html#the-div-element) 和 span(https://www.w3.org/TR/html52/textlevel-semantics.html#the-span-element) 元素的“省略标签”部分标注的都是:开始和结束标签都不能省略(Neither tag is omissible)。

因此浏览器在最后补上了对应的结束标签 </span></div>。所以我们看到的现象就是 span 元素被 div 元素包裹起来了:<div><span></span></div>


正确处理 HTML 5 标签的关闭
HTML 5 的 XML 语法


那有没有办法在 HTML 5 中也保持原来 XHTML 1 相对规范的写法风格呢,答案是肯定的。HTML 5 规范中规定了 XML 语法(XML syntax(https://www.w3.org/TR/html52/xhtml.html#xhtml)(或称为 XHTML 语法 XHTML syntax,很多地方提及的 XHTML5 指的就是 HTML 5 的 XML 语法,实际上目前并没有 XHTML5 这种东西)。

简单的说,通常情况下,我们的 HTML 文件使用的是 text/html 的 MIME Type,这将告诉接收方(如浏览器)HTML 文档使用的是 HTML 语法(https://www.w3.org/TR/html52/syntax.html#syntax);而当 MIME Type 为 application/xhtml+xml 或 application/xml,并在 HTML 中声明对应的 XML 命名空间时:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>HTML5 Document with XML Syntax</title>
</head>
<body>
<div/><span/>
</body>
</html>

正确处理 HTML 5 标签的关闭

浏览器按 XML 语法来解析 HTML,自闭合标签解析成为成对的标签了。在 XML 语法中,可以像 XHTML 1 规范的那样按 XML 风格编写 HTML 5,并且可以使用 HTML 5 新增的诸多标签等。而且当 HTML 中出现不规范的地方时,浏览器仍然会停止解析并显示错误信息。


正确处理 HTML 5 标签的关闭
总结


在 XHTML 中,我们必须严格遵守 XML 的写法,所有标签都必须关闭,通过自闭合(<br />)的方式或者书写结束标签(<span></span>)都可以。但如果不闭合标签,则会收到相关错误。

在 HTML 5 中,分为 HTML 语法和 XML 语法。HTML 语法宽松,可以像 HTML 4 一样编写 HTML,浏览器会忽略自闭合的标签(<br />  <br>),或者补全一些未闭合的标签(<p>p1<div></div>  <p>p1</p><div></div>,但需要注意开始结束标签不可省略的元素一定要写全,否则可能会解析出预期外的 HTML(<div /><span />  <div><span></span></div>)。而在 XML 语法下,可以像 XHTML 一样遵守 XML 写法。

借由 jQuery 一个中断性变更,我们通过阅读 HTML 规范文档,加深了对标签闭合部分规定的了解。但更重要的一点是,虽然 HTML 5 的 HTML 语法宽松,但作为开发者,我们还是应该严格要求自己,区分好 HTML 5 和 XHTML 的差异,写出符合规范的代码,避免浏览器解析出不符合预期内容的情况。


参考


  • jQuery Core 3.5 Upgrade Guide:https://jquery.com/upgrade-guide/3.5/

  • XHTML™ 1.0 The Extensible HyperText Markup Language (Second Edition):https://www.w3.org/TR/xhtml1

  • 空元素:https://developer.mozilla.org/zh-CN/docs/Glossary/empty_element

  • HTML 4.01 Specification:https://www.w3.org/TR/html401/intro/sgmltut.html#h-3.2.1

  • HTML 4.01 Specification (19991224):https://www.w3.org/TR/1999/REC-html401-19991224/intro/sgmltut.html#didx-element-4

  • 漫画:混乱的标记语言XHTML2/HTML5(附中文版翻译):https://www.cnblogs.com/JustinYoung/archive/2009/07/30/misunderstanding-markup-xhtml-2-comic-strip.html

  • HTML 5.2:https://www.w3.org/TR/html52/xhtml.html#xhtml

  • HTML5学习笔记简明版(1):HTML5介绍与语法:https://www.cnblogs.com/TomXu/archive/2011/12/06/2269008.html


如果对您有帮助
可以的话,别忘了“喜欢作者”哦