开发专栏
正确处理 HTML 5 标签的关闭
4 年前
5855
前端开发

本文通过阅读HTML4、XHTML和HTML5规范,结合jQuery3.6升级的中断性变更,来了解HTML的标签到底应该如何正确的关闭。

背景

在将公司一款使用 jQuery 的产品升级到最新的 jQuery 3.6 之后发现部分页面出现了排版异常的问题,查阅了通过 jQuery 生成的 HTML 和 jQuery 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> 标签。

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

XHTML

记得我第一次系统性的学习 HTML 是从学校图书馆借到了一本名为《HTML与CSS网站设计实践之旅》的书开始的,这是一本非常好的入门书,结构清晰上手简单,感谢这本书奠定了我的 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 来改进代码的编写和浏览器解析的问题。

格式良好(Well-formed)

因为 XHTML 必须是格式良好(Well-formed的,所以书写规范和 XML 一样,例如标签必须成对出现,不能只有开始标签而没有结束标签。

这是一段来自 HTML 4.01 规范中介绍元素结束标记可以忽略的代码,这样的代码是合法的:

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

但在 XHTML 中指出:

For non-empty elements, end tags are required.

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

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

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

html4-vs-xhtml

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

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

空元素

在 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 不包含任何内容;它的唯一作用是终止一行文本。这样的元素永远不会带有结束标签。

也就是说,空元素(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 因为承袭了 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

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

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

HTML 5

很多资料上在介绍 HTML 5 时都会提及一句,与 HTML 5 竞争的 XHTML 2 规范止于草案,未能成为正式规范的故事,一则漫画很好的解释了 XHTML、XHTML 2 和 HTML5 的关系:混乱的标记语言XHTML2/HTML5(Misunderstanding Markup: XHTML 2/HTML 5 Comic Strip)中文翻译版 | 英文原版

HTML 5 让 HTML 的编写习惯又回到了 XHTML 之前的宽松年代,标签不再必须小写、关闭标签不再强制……但很多 HTML 开发者更喜欢 XHTML 1 时代规范的写法,不过这两种方式 HTML 5 都包容。

话题回到标签闭合上,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 元素的开始标签设为自动关闭。

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

Void 元素

HTML 5 规范将 HTML 元素分为6大类,其中就规定了哪些元素属于void 元素

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

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

可以对比一下 void 元素之一的 br 元素 与常规元素之一的 p 元素 在 HTML 5 规范中“省略标签”(tag omission)部分的描述:

  • The br element
    • No end tag
    • (没有结束标签)
  • The p element
    • A <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> 的结束标签在一些情况下是可以省略的。

html5

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

  • 在段落 p1 中,本来没有关闭该段落,但因为紧跟了一个 <div> 标签,所以浏览器自动生成了一个 </p> 来关闭该段落
  • 在段落 p2 中,自闭合形式书写的 <br /> 被解析为了 <br>

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

<div/><span/>

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

而且在 HTML 5 规范中,divspan 元素的“省略标签”部分标注的都是:开始和结束标签都不能省略(Neither tag is omissible)。

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

HTML 5 的 XML 语法

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

简单的说,通常情况下,我们的 HTML 文件使用的是 text/html 的 MIME Type,这将告诉接收方(如浏览器)HTML 文档使用的是 HTML 语法;而当 MIME Type 为 application/xhtml+xmlapplication/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>

html5-xhtml-syntax

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

总结

在 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 的差异,写出符合规范的代码,避免浏览器解析出不符合预期内容的情况。

参考