DOCTYPE:文档类型与浏览器模式
浏览器打开页面时,先看文档第一行的 <!DOCTYPE>,再决定按现行 HTML、CSS 规范排,还是按当年 Netscape Navigator、Internet Explorer 的老行为排。声明写错、写残或干脆不写,同一套 CSS 在不同引擎里会对不上。它不是一个带结束标签的 HTML 元素,而是写在文档最顶端的声明指令。
文档类型定义(DTD)
DTD(Document Type Definition,文档类型定义)是一套语法规则,用来定义 XML 或 (X)HTML 的文件类型:允许哪些元素、它们有哪些属性、彼此如何嵌套。
在 SGML、XML 体系里,解析器可以拿 DTD 做合法性验证。浏览器对 HTML 并不去下载那份 DTD 做校验,而是根据 <!DOCTYPE> 字符串切换渲染模式。
DTD 理论上能定义整个 SGML 语族的文档类型,规则又长又繁。后来更流行的是 XML 和 HTML,不再把完整 DTD 挂在每个页面前面。
什么是 SGML
SGML 是标准通用标记语言(Standard Generalized Markup Language),是国际上定义电子文件结构和内容描述的标准。它不是一门具体的标记语言,而是一门「元语言」(Metalanguage):用来定义其他标记语言的语言。HTML 4.01 及更早的 HTML 是按 SGML 来定义的;HTML5 已经不基于 SGML。
SGML 的三个组成部分:
- SGML 声明(SGML Declaration):指定构建文档的基本参数。
- 文档类型定义(DTD):这是 SGML 的核心。DTD 定义一门新语言的合法「语法」,用一系列规则明确规定文档中可以有哪些元素、如何嵌套,以及哪些属性是必要的。
- 文档实例(Document Instance):实际撰写的、包含数据和标记的文档文件。
DOCTYPE 的作用
DOCTYPE 是 Document Type(文档类型)的简写,完整说法是文档类型声明(Document Type Declaration)。它用来声明文档类型和所依据的 DTD 规范,历史上承担两件事:文件的合法性验证,以及告诉浏览器按哪套规则解析文档。
在 HTML 源码里,<!DOCTYPE> 严格说来不是一个 HTML 标签,只是一条声明指令,用来告诉浏览器当前这份代码按哪一种 HTML 或 XHTML 规范来理解。
<!DOCTYPE> 真正决定浏览器以标准模式、接近标准模式还是怪异模式来解析文档。
做页面时必须让浏览器知道这份 HTML 用的是哪一种规范,显示才会按现行规则走。没有声明时,浏览器按怪异模式解析;各浏览器的怪异模式并不相同(按各自内置的旧排版规则),同一份页面在不同浏览器里会排出不一样的结果。
DOCTYPE 组成部分
HTML 4、XHTML 时代的完整声明可以拆成下面几段。HTML5 的 <!DOCTYPE html> 不再带这些字段。
<!DOCTYPE 根元素 可用性 "注册//组织//类型 标签 定义//语言" "URL">
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- 根元素:指定 DTD 中声明的顶级元素类型,这与声明的 SGML 文档类型相对应,默认
HTML。 - 可用性:指定正式公开标识符(FPI,Formal Public Identifier)是可公开访问的对象还是系统资源。默认
PUBLIC,表示可公开访问。SYSTEM表示系统资源,如本地文件或 URL。 - 注册:指定组织是否由国际标准化组织(ISO)注册。默认
+,组织名称已注册。-表示组织名称未注册。IETF 和 W3C 都不是注册的 ISO 组织,所以常见写法以-//开头。 - 组织:指定负责创建和维护该 DTD 的团体或组织名称,即 OwnerID。常见是 IETF 或 W3C。有的资料写成 OwnderID,那是拼写错误。
- 类型:指定公开文本类,即所引用的对象类型。默认
DTD。 - 标签:指定公开文本描述,即对所引用公开文本的唯一描述性名称,后面可附带版本号。默认
HTML。 - 定义:指定文档类型定义。
Strict(排除所有 W3C 希望逐步淘汰的表现性属性和元素)、Transitional(包含除frameset之外的全部内容)、Frameset(框架集文档)。 - 语言:指定公开文本语言,即编写所引用对象时用的自然语言编码。按 ISO 639 写成两个大写字母。默认
EN。 - URL:指定所引用对象的位置,也叫系统标识符(System Identifier)。有无这一段,会改变部分旧 DOCTYPE 触发的是怪异模式还是接近标准模式,见后文。
DOCTYPE 的用法
- 写在
<html>之前。 - 必须放在文档最顶部,在所有代码和标识之上。HTML、XHTML 都一样。旧版 IE 里,若在它前面插了注释、XML 声明(
<?xml version="1.0"?>)或 BOM,仍会掉进怪异模式。 - 没有结束标签。
- 在 HTML 语法里对大小写不敏感,
<!DOCTYPE html>、<!doctype html>效果相同。作为application/xhtml+xml解析的 XHTML 按 XML 规则处理,声明要符合 XML。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
hello world
</body>
</html>
常用 DOCTYPE 声明
HTML 里常见的 <!DOCTYPE> 声明类型共有 8 种:HTML5 1 种,HTML 4.01 和 XHTML 1.0 各 3 种,XHTML 1.1 1 种。
HTML5 <!DOCTYPE> 声明
HTML5 只规定一种声明,能向前、向后兼容,代码也更简洁。HTML5 不是基于 SGML,因此不要求引用 DTD。现行页面推荐这一条。
<!DOCTYPE html>
IE 6 也会把它当成标准模式。新项目没有理由再写更长的 DOCTYPE。
HTML 4.01 <!DOCTYPE> 声明
HTML 4.01 规定了三种不同的 <!DOCTYPE> 声明:
- Strict:包含所有 HTML 元素和属性,但不包括展示性的和弃用的元素(比如
font)。不允许框架集(Framesets)。 - Transitional:包含所有 HTML 元素和属性,包括展示性的和弃用的元素(比如
font)。不允许框架集(Framesets)。 - Frameset:等同于 HTML 4.01 Transitional,但允许框架集内容。
<!--HTML 4.01 Strict-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!--HTML 4.01 Transitional-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!--HTML 4.01 Frameset-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
XHTML 1.0 <!DOCTYPE> 声明
XHTML 1.0 也规定了三种不同的 <!DOCTYPE> 声明:
- Strict:包含所有 HTML 元素和属性,但不包括展示性的和弃用的元素(比如
font)。不允许框架集(Framesets)。必须以格式正确的 XML 来编写标记。 - Transitional:包含所有 HTML 元素和属性,包括展示性的和弃用的元素(比如
font)。不允许框架集(Framesets)。必须以格式正确的 XML 来编写标记。 - Frameset:等同于 XHTML 1.0 Transitional,但允许框架集内容。
<!--XHTML 1.0 Strict-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--XHTML 1.0 Transitional-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--XHTML 1.0 Frameset-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
XHTML 1.1 <!DOCTYPE> 声明
XHTML 1.1 规定了一种声明,等同于 XHTML 1.0 Strict,但按模块化(Modularization)组织,允许按模块增减能力。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
其他 <!DOCTYPE> 声明
这些更旧或更窄的声明,现行浏览器多半仍能用来切换模式,新页面不必再用。
<!-- HTML 4.0 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- HTML 4.0 Frameset -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<!-- HTML 4.0 Transitional -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML 3.2 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<!-- HTML 2.0 -->
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<!-- XHTML Basic 1.0 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">
<!-- XHTML Basic 1.1 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
<!-- XHTML 2.0 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 2.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml2.dtd">
上面 HTML 4.0 三条里的系统标识符沿用了 html4/ 路径,实际对应的是后来 HTML 4.01 的 DTD 文件;HTML 4.0 当年的路径是 http://www.w3.org/TR/REC-html40/。XHTML 2.0 没有成为继任标准,工作已被 HTML5 取代。有的资料会把 Basic 1.1 的 DTD 也标成 1.0。
命名空间 namespace
<html xmlns="http://www.w3.org/1999/xhtml">
xmlns 是 XML namespace 的缩写,称为命名空间。它在 XHTML 中是必要的,在 HTML 中则是可选的。以 text/html 发送的 HTML5 里,根元素始终处于 HTML 命名空间,xmlns 写了也不改变解析方式;内嵌的 SVG、MathML 靠 HTML 解析器的外文内容规则识别。以 application/xhtml+xml 发送的 XHTML 必须按 XML 带上命名空间。
XHTML 是 HTML 向 XML 过渡的标记语言,需要符合 XML 文档规则,因此也需要定义命名空间。XHTML 1.0 不能自定义元素名,所以它的命名空间都相同,就是 http://www.w3.org/1999/xhtml。
xmlns 属性可以在文档中定义一个或多个可供选择的命名空间,可以放置在文档内任何元素的开始标签中。
xmlns 提供避免元素命名冲突的方法。XML 中的元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突。
如果需要使用符合 XML 规范的 XHTML 文档,则应该在文档中的 <html> 标签中至少使用一个 xmlns 属性,以指定整个文档所使用的主要命名空间:
<html xmlns="http://www.w3.org/1999/xhtml">
如果需要在一个 div 元素中显示一串数学公式,则可以为该 div 元素定义一个数学命名空间。比如这样(示意默认命名空间怎么挂到子树上;真正排版公式应使用 MathML 元素,而不是把普通 div 改个命名空间):
<div xmlns="http://www.w3.org/1998/Math/MathML">x3/x</div>
如果不希望在每次显示除法公式时都在 div 元素中定义 xmlns 属性,那么更好的办法是在文档的开头处定义具有前缀的命名空间:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
然后就可以在子元素上使用该前缀了,就像这样:
<math:div>x3/x</math:div>
这只说明「前缀 + 本地名」怎么写。合法 MathML 根元素是 math,不是 div。MathML 的命名空间是 http://www.w3.org/1998/Math/MathML(注意是 1998,且 MathML 大小写如此)。有的资料写成 http://www.w3.org/1999/Math/MathMl,地址和大小写都不对。
常用的命名空间值:
- HTML:
http://www.w3.org/1999/xhtml。 - MathML:
http://www.w3.org/1998/Math/MathML。MathML 指数学标记语言,是 XML 语言的一个子集,用来在 Web 网页,甚至部分软件中显示数学公式。简言之,就是使用特殊的类似 HTML 的标记在网页中显示数学公式。 - SVG:
http://www.w3.org/2000/svg。 - XLink:
http://www.w3.org/1999/xlink。有的资料写成xllink。XLink 是一种通过 W3C 推荐标准 认证的 XML 标记语言,用于在 XML 文档中创建超链接,以及提供与这些链接相关联的元数据。 - xml:
http://www.w3.org/XML/1998/namespace。这是 XML 规范预留的xml:前缀所属命名空间,例如xml:lang、xml:id,不必、也不应自己再声明一遍xmlns:xml。 - xmlns:
http://www.w3.org/2000/xmlns/。这是xmlns与xmlns:前缀本身所属的命名空间,由 XML 命名空间规范保留,页面里不要用它当普通元素的命名空间。
怪异模式和标准模式
很久以前的网页通常要准备两个版本:给网景(Netscape)Navigator 的,以及给微软(Microsoft)Internet Explorer 的。W3C 把网络标准立起来之后,浏览器不能直接丢掉那些已经上线的老站。于是引擎同时保留两套(后来是三套)排版行为,用 DOCTYPE 把「按新规范写的站」和「按老浏览器写的站」分开。
目前浏览器的排版引擎使用三种模式:
- 怪异模式(quirks mode):排版会模拟 Navigator 4 与 Internet Explorer 5 的非标准行为,用来撑起网络标准普及之前就建好的网站。
- 标准模式(standards mode,现行规范称 no-quirks mode):行为按现行 HTML 与 CSS 规范来。
- 接近标准模式(almost standards mode,现行规范称 limited-quirks mode):只实现极少数怪异行为,主要是表格单元格里图片的行高、基线对齐,避免单元格底部多出一条缝。
浏览器如何决定使用哪个模式
对 HTML 文件来说,浏览器使用文件开头的 DOCTYPE 来决定用哪一种模式。以 application/xhtml+xml 发送的真正 XHTML 按 XML 解析,不靠这段声明在怪异、标准之间切换。
以下情况浏览器会采用标准模式(no-quirks)渲染:
- 给出了完整且会触发标准模式的 DOCTYPE,例如 HTML5 的
<!DOCTYPE html> - DOCTYPE 声明了 Strict DTD(如 HTML 4.01 Strict、XHTML 1.0 Strict)
以下情况浏览器会采用接近标准模式(limited-quirks)渲染:
- DOCTYPE 声明了 Transitional 或 Frameset DTD,并且给出了 URI(系统标识符)
以下情况浏览器会采用怪异模式(quirks)渲染:
- DOCTYPE 声明了 Transitional 或 Frameset DTD 但未给出 URI
- DOCTYPE 声明不合法
- 未给出 DOCTYPE 声明
有的资料把「Transitional DTD 且带 URI」也归进标准模式。现行引擎里它走的是接近标准模式:document.compatMode 仍是 CSS1Compat,和标准模式无法用这一属性区分,差别主要在表格单元格里的图片布局。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
</body>
</html>
<!DOCTYPE html> 是所有可用的 DOCTYPE 之中最简单的,也是 HTML5 所推荐的。HTML 的早期变种也曾是推荐标准,不过当今的浏览器都会对这个 DOCTYPE 使用标准模式,就算是早已过时的 Internet Explorer 6 也一样。目前没有正当理由去使用其他更复杂的 DOCTYPE。若使用其他 DOCTYPE,会面临触发接近标准模式或者怪异模式的风险。
在 HTML5 中,DOCTYPE 唯一的作用是启用标准模式。更早期的 HTML 标准中,DOCTYPE 还会指向一份 DTD,供校验器核对合法性。浏览器并不会去拉那份 DTD 做校验,也不会把 DOCTYPE 用在渲染模式切换以外的用途。
怪异模式和标准模式的不同点
盒模型的处理差异
标准 CSS 盒模型的宽度和高度等于内容区的宽度和高度,不包含内边距和边框(即默认的 content-box)。
IE 5 以及怪异模式下的旧 IE 把内边距和边框算进 width、height,接近后来的 box-sizing: border-box。
因此,IE 5.5 及以下,以及其他版本处于怪异模式时,和标准模式下的盒模型宽高计算方式不一样。IE 6 起,有正确 DOCTYPE 时按标准内容盒计算。
行内元素的垂直对齐
很多早期的浏览器把图片对齐到包含它们的盒子的下边框,虽然 CSS 规范要求它们对齐到盒内文本的基线。
标准模式下,基于 Gecko 的浏览器会对齐到基线,而在怪异模式下会对齐到底部。
最直接的例子就是图片的显示。在标准模式下,图片并不是与父元素的下边框对齐的,如果仔细观察,你会发现图片与父元素下边框之间存在一点小空隙。那是因为标准模式下,图片是基线对齐的。而怪异模式下,则不存在这个问题。接近标准模式保留的,主要也是表格单元格里这一条。
获取视口尺寸
// 标准模式下,任意浏览器都兼容:
document.documentElement.clientWidth
document.documentElement.clientHeight
// 怪异模式下的浏览器:
document.body.clientWidth
document.body.clientHeight
怪异模式量客户区、读 scrollTop 要用 document.body。标准模式要用 document.documentElement(<html>)。
行内元素的高宽
在标准模式下,给 span 等行内元素设置 width 和 height 都不会生效,而在怪异模式下则会生效。
百分比的高度
在标准模式下,一个元素的高度是由其包含的内容来决定的,如果父元素没有设置高度,子元素设置一个百分比的高度是无效的。怪异模式下,旧 IE 对 html、body 的百分比高度更宽松。
margin: 0 auto 水平居中
使用 margin: 0 auto 在标准模式下可以使元素水平居中,但在旧 IE 的怪异模式下会失效。解决办法是用父元素的 text-align 属性。
图片的 padding
旧 IE 的怪异模式下,给图片设置 padding 会失效。
表格中的字体继承
旧 IE 的怪异模式下,table 中的字体属性不能继承上层的设置。
white-space: pre
旧 IE 的怪异模式下,white-space: pre 会失效。
相关问题
为什么 HTML5 只需要写简短的一句
HTML 4.01 及更早是基于 SGML 定义的,所以要加上 DTD 的引用。
HTML5 不基于 SGML,因此不需要对 DTD 进行引用,但是需要 DOCTYPE 来规范浏览器的行为,让浏览器按照它们应该的方式来运行(进入标准模式)。
如何查看目前是哪个模式
用 JavaScript 判断是哪种模式。
document.compatMode // BackCompat:怪异模式 // CSS1Compat:标准模式或者接近标准模式CSS1Compat无法区分标准模式和接近标准模式。在 Firefox 中,请从右键菜单选择「查看页面信息」,然后查看「渲染模式」。
在 Internet Explorer 中,请按下 F12,然后查看「文档模式」。
结语
现行页面在第一行写 <!DOCTYPE html>,引擎就会走标准模式。DOCTYPE 不再负责把 DTD 拉下来做校验,只负责把排版从旧浏览器的兼容行为里切出来。遇到对不齐的盒模型、图片底下一条缝、clientWidth 读错节点,先看有没有声明、声明会不会掉进怪异模式或接近标准模式。