你打开一个网页,右键查看源代码,标记第一眼看到的档可多对大多是<!DOCTYPE html>,接着是标记<html>,然后<head>里塞着标题、档可多对字符集、标记样式表。档可多对几乎所有的标记教程都告诉你:head只能有一个。可你如果真去翻HTML规范,档可多对会发现一个反直觉的标记事实——浏览器实际上允许文档里出现多对<head>标签。这不是档可多对漏洞,也不是标记什么冷门技巧,它写在解析规则里。档可多对
先别急着关页面。标记我去年调试一个老旧的档可多对CMS模板时,就撞上了这个情况。那个模板在<body>中间莫名其妙又插了一个<head>,里面还带着一个<title>和一段<style>。我当时以为页面会崩,结果Chrome和Firefox都正常渲染了,只是样式被合并了,标题则用了第一个<head>里的。这让我花了半小时去翻WHATWG的HTML解析算法,才发现浏览器处理多个head的方式非常务实。
test.html,用浏览器打开。<!DOCTYPE html><html><head><title>第一个标题</title></head><body><p>正文段落</p><head><title>第二个标题</title><style>body{ background:#ffeaa7;}</style></head></body></html> 你会发现页面背景变成了淡黄色,但标签页上的标题依然是“第一个标题”。第二个<head>里的<style>生效了,<title>却被忽略了。原因在于HTML解析器遇到文档中第二个<head>时,会把它当作“在body中出现head”来处理——解析器不会报错,而是把里面的元素按规则插入到DOM里。具体来说,<style>、<link>、<meta>这些元素会被移到文档的head部分(或者就地生效),而<title>因为已经有一个了,就被丢弃。这跟浏览器厂商的容错策略有关,不是标准强制要求,但所有主流引擎都这么干。
那为什么规范不直接禁止多个head?我猜是因为HTML从一开始就带着“宽松解析”的基因。早期网页作者经常写错嵌套,浏览器如果一遇到重复head就罢工,互联网早就碎成渣了。于是解析算法里专门有一条:in body插入模式遇到<head>标签时,把它当作普通未知元素处理,但里面的子元素会按特定规则重新分配。这种设计让老页面能继续跑,代价就是行为有点古怪。
实际开发中几乎没人会故意写两个<head>。可如果你用JavaScript动态拼接HTML字符串,或者用某些模板引擎做局部刷新,就可能无意中生成这种结构。我见过一个前端框架的早期版本,在异步加载组件时把组件的<style>块包在<head>里直接插到<body>末尾,结果样式确实生效了,但控制台里没有任何警告。这算是一种“灰色地带”的用法——能用,但不该依赖。
更奇怪的是,如果你在<head>里面再套一个<head>,比如<head><head><title>x</title></head></head>,解析器会直接忽略内层的<head>标签,只保留里面的内容。这跟多个平级<head>的行为又不一样。我拿不同的浏览器试过,Chrome、Firefox、Safari在这点上表现一致,Edge基于Chromium自然也一样。所以别想着靠嵌套head来搞什么黑科技。
写这篇文章不是鼓励你去写多个<head>。恰恰相反,你应该始终保证文档只有一个<head>,并且放在<html>开头。但知道浏览器能容忍什么,有助于你在调试别人留下的烂摊子时少掉几根头发。下次看到页面里冒出第二个<head>,别慌,先看看里面的<style>和<meta>有没有生效,再决定要不要重构。毕竟,能跑起来的代码不一定是对的,但知道它为什么能跑,你就比大多数人强了。