ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HTML标签关系、注释与属性:前端新人必学的三个基本功

HTML标签关系、注释与属性:前端新人必学的三个基本功

写HTML看起来是前端开发里最没有门槛的事,标签套标签,会打字就能上手。但我见过太多人学了大半年,写出来的页面结构一塌糊涂:标签嵌套没逻辑、代码里没有一条注释、属性随手乱写。浏览器倒是都能打开,改起样式来却像在盲人摸象。所以这期笔记,我想把HTML标签之间的关系、HTML注释、标签属性这三个基本功一次性聊透。别小看这三样东西,很多写了两年页面的前端都不一定搞清楚了。它们不像JavaScript那么考验逻辑,也不像CSS那么讲究设计感,但它们决定了你的页面结构干不干净、代码好不好维护、浏览器解析会不会出错。

如果你是刚入门H5开发的新手,这一期建议从头看到尾。内容不深,但每一条都是日常写页面时高频踩中的点。如果你已经写了一段前端代码,也可以重点看后面的“避坑”和“实战”部分,对照一下自己平时有没有犯过类似的问题。

1. 先搞懂HTML标签之间的“家庭关系”

1.1 父子关系:谁包着谁,谁就是“父”

HTML本身就是一个嵌套结构,标签套标签,就像文件夹套文件夹一样。我们把“直接包裹其他标签”的标签称为父元素,被包裹在里面的那一个称为子元素。

<div> <p>我是子元素</p> </div>

在这个例子里,<div>就是<p>的父元素,<p>是<div>的子元素。这个关系是直接的、相邻的,中间没有隔其他标签。如果<p>里面又塞了一个<span>,那么<div>相对于<span>来说就不再是父元素,而是祖先元素。同理,<span>相对于<div>是后代元素。

这个关系能不能再通俗一点?你把它想象成家庭族谱:

  • 父元素 = 爸爸
  • 子元素 = 儿子
  • 祖先元素 = 爷爷、太爷爷
  • 后代元素 = 孙子、重孙子

很多新手搞混的地方是“父元素”和“祖先元素”的区别。举个真实场景:你在CSS里写div p { color: red; },这个选择器是把div里面所有的p(不管隔多少层)都选中了;但如果写的是div > p { color: red; },就只选中div直接包住的那一层p。这两个效果完全不同,根源就是在标签关系的理解上。

1.2 兄弟关系:同一层级的小伙伴

有上下级,就有平级。同一个父元素下的、处于同一层级的标签之间,称为兄弟元素。

<div> <h1>标题</h1> <p>第一段</p> <p>第二段</p> </div>

这里的<h1>和两个<p>都是兄弟关系。<p>和<p>之间也是兄弟。当然,我们说的“兄弟”不是真的有血缘关系,只是在DOM树里处在同一层罢了。

理解兄弟关系有什么用?先说CSS。像h1 + p { margin-top: 0; }这种相邻兄弟选择器,用的就是“两个标签是紧挨着的兄弟”这个前提。再说JavaScript,你通过document.querySelector('.item').nextElementSibling拿到下一个兄弟节点,也是在操作这层关系。如果你脑子里没有兄弟这个概念,后面学DOM操作很容易绕晕。

还有一层作用在页面结构上。一个父元素里如果有多个兄弟元素,它们默认会按照从上到下的顺序在页面里排列。块级元素会占满整行,一个接一个往下排;行内元素会从左往右排。这个“排列规则”虽然是由CSS决定的,但HTML里的层级关系是它的大前提。

1.3 嵌套规则:哪些标签不能“乱伦”

标签的关系不是你想怎么套就怎么套,HTML规范对“谁里面能放谁”有明确要求,专业一点的叫法是内容模型(Content Model)。这里我不把规范条文全部搬出来,只讲日常开发中最常踩的几个硬性规则:

  1. <p>里面不能放块级元素。<p>是段落标签,段落里应该是文字、<span>、<a>、<img>这类行内内容。如果你在<p>里面塞一个<div>,浏览器会自动把<p>在<div>前“切断”,导致结构错乱。

  2. <ul>和<ol>的直接子元素只能是<li>。也就是说<ul>外面可以套其他标签,但<ul>里面第一层就是<li>。如果你直接写<ul><div>xxx</div></ul>,浏览器虽然不会报错,但渲染出来的DOM结构会和预期不一样。

  3. <a>标签不能嵌套<a>标签。这一点非常隐蔽,很多人写卡片式链接,在<a>里面又套了<a>,结果点击区域错乱、跳转失效。为什么不能套?因为浏览器解析到第二个<a>时,会自动闭合第一个<a>,等于你写了一层,但实际DOM里被拆成了两层,完全失去本意。

  4. <button>里面不能放<button>、<input>、<select>等交互元素。否则会出现事件冲突,比如点击内层按钮,外层按钮也响应了。

这些规则不是浏览器故意为难你。HTML的设计原则是“容错优先”,浏览器在遇到不合法的嵌套时会自动纠正,但纠正的结果往往不是你想要的。很多“页面看起来正常但样式对不上”的诡异问题,根源就是标签嵌套不规范。

2. HTML注释:写给代码的“便利贴”

2.1 注释的基本写法与快捷键

HTML注释看起来没什么技术含量,格式只有一种:

<!-- 这是一段注释,不会显示在页面上 -->

注释可以写在任意位置,可以占一行,也可以跨多行:

<!-- 作者:某开发者 日期:2025-01-10 用途:产品列表区域 --> <div class="product-list"> <!-- 单个商品卡片 --> <div class="product-item">...</div> </div>

编辑器里一般都有注释快捷键:

  • VS Code / Sublime / WebStorm 等:Windows 用Ctrl + /,Mac 用Cmd + /
  • 选中多行再按快捷键,可以一次性注释多行;再按一次,取消注释

刚开始学的时候,很多人觉得注释“写了跟没写一样,反正用户看不见”。但注释是写给写代码的人看的,这个“人”包括未来的你、你的同事、你的团队成员。页面最终展示给用户的是渲染之后的HTML,但代码从诞生到维护、重构,整个过程都是人在看,不是浏览器在看。

2.2 注释的三种实用场景

我自己的习惯是,只要代码里出现下面三种情况之一,就一定会写注释:

第一,标记模块的边界。一个真实页面动辄几十个区块,代码写到后面你自己都忘了哪个</div>对应哪个开头。这时候用注释把“门户”标出来:

<!-- ===== 头部导航栏 start ===== --> <header> <nav>...</nav> </header> <!-- ===== 头部导航栏 end ===== -->

配合编辑器的代码折叠功能,整个页面的结构一目了然。这个习惯在团队协作里尤其重要,别人接手你代码的时候,不用从头一行一行猜。

第二,临时禁用代码做调试。页面渲染出问题,你想确认是不是某一块代码引起的,不用删掉它,直接注释掉。刷新浏览器看看效果,确定问题所在再决定去留。这个操作比反复删了又粘贴回来要高效得多。

第三,留“提醒”给未来的自己。比如某段结构写得比较绕,或者有已知的兼容性问题,你可以在旁边注释一句:

<!-- 注意:这里不要改成 div,会影响后台管理系统的样式覆盖 --> <div class="card-item">...</div>

我见过很多项目上线几个月后,有人乱改一段看不懂的代码导致线上样式崩了,翻提交记录才发现半年前有人在注释里写了“不要动这里”。写注释的成本极低,收益却很高。

2.3 注释的禁忌

注释也有几个容易踩的坑。

  • 注释里不能嵌套注释。HTML注释不支持嵌套,你在<!-- ... -->里面再写<!-- ... -->,第一个-->会把整段提前闭合,后面内容全部暴露出来,直接变成页面上的乱码。

  • 注释内容里不要出现两个连续的连字符--。HTML规范要求注释内容不能含有--。有的字符串文本里带了,浏览器解析时就可能出错。我见过有人在注释里写时间范围“2023--2024”,结果页面结构乱了,排查了半天才找到这条注释。

  • 注释不能写在标签内部。比如<div <!-- 注释 -->>,这会让浏览器解析混乱,标签直接失效。注释只能写在标签外面,或者标签对之间的位置。

3. 标签属性:给HTML元素加“参数配置”

3.1 属性的构成与写法规范

如果说标签定义了元素“是什么”,那属性就规定了元素“怎么配置”。标签属性写在开始标签内部,由“属性名”和“属性值”组成:

<a href="https://www.example.com" target="_blank">访问示例网站</a>

结构拆开看:

  • href:属性名
  • "https://www.example.com":属性值
  • target:第二个属性名,多个属性之间用空格隔开
  • "_blank":它的属性值

写属性有几个硬性规范,照着做不容易出错:

  1. 属性名小写,虽然HTML5对大小写不敏感,但统一小写是行业习惯,也是团队协作的基础。
  2. 属性值用双引号包起来。单引号也可以,但混用会导致解析歧义。比如href='https://example.com'合法,但你写href='https://example.com"就会出问题。
  3. 属性名和属性值之间用等号连接,等号两边不要随便加空格。写成href = "url"在多数浏览器里能容错,但规范上不推荐,而且会干扰后续的代码格式检查。

再说说属性值里的特殊字符。最典型的是链接地址里带了&参数,比如:

<a href="https://example.com/search?keyword=a&page=2">搜索</a>

这个&符号在HTML里有特殊含义,如果直接用会带来不可控的解析风险。规范做法是写成实体字符&amp;:

<a href="https://example.com/search?keyword=a&amp;page=2">搜索</a>

这段可能有人看不懂,我举个生活化的例子:属性值里的双引号是做“外壳”的,&就是藏在壳里容易被误拆的“定时炸弹”,不转义它,浏览器会以为自己看到了新的标签起点。

3.2 全局属性:所有标签都能用的“通用配置”

有一类属性是几乎所有标签都可以使用的,叫做全局属性。它们不依赖具体的标签,是HTML这门语言统一提供的“基础设施”。

属性名作用典型使用场景
id定义元素在页面中的唯一标识CSS选择器、JavaScript定位、锚点跳转
class定义元素的样式类名,可多个CSS样式复用,最常用的属性之一
title鼠标悬停时显示的提示文字给用户补充说明
style直接在HTML里写CSS样式临时调整、测试样式,不推荐大量使用
><div class="card card-active card-large">...</div>

这表示这个元素同时拥有card、card-active、card-large三个类。CSS里可以分别选中它们,也可以组合选中,灵活度非常高。需要注意的是,属性值里的空格是“分隔符”,所以类名本身不要包含空格,也不能用中文或特殊符号。

第三,><div class="product"><input type="checkbox" checked="false">

看起来像是“不选中”,实际上浏览器只认“标签里有没有checked这个属性”,所以它的结果是选中。想“不选中”,唯一的写法是把这个属性整个去掉。

这个特性在React等框架里还不太一样,框架里checked={false}是受控写法,确实能取消选中。但在原生HTML里,写checked="false"就是个无效操作。背景不同的项目,规则不同,注意区分。

4. 实战中那些“不报错但很致命”的坑

4.1 嵌套错误的典型案例

先看一个最常见的错误写法:

<p> 这是一段文字 <div>我放在段落里的块级元素</div> </p>

你觉得这段代码在浏览器里是什么结果?答案是:浏览器会自动把<p>在<div>前闭合,变成这样:

<p>这是一段文字</p> <div>我放在段落里的块级元素</div> <p></p>

最后面还会多一个空的<p>标签。这会导致什么后果?如果你用CSS给<p>设置了text-indent首行缩进、line-height行高、或者margin间距,这些样式会作用在一个多余且空白的标签上,页面出现莫名奇妙的空白区域。而你以为自己写的是一个“段落里嵌了一个分区”,实际渲染的却是三段完全不同的结构。

还有一个隐蔽的例子是表格嵌套:

<table> <div>不能直接放div</div> <tr> <td>内容</td> </tr> </table>

标准的表格结构应该是<table>下直接是<thead>、<tbody>、<tfoot>,然后里面才是<tr>,再是<td>。如果你在<table>下面直接写<div>,浏览器会把这个<div>踢出表格结构,渲染到表格外面去。布局位置完全不受你控制。

我在团队里带人的时候,处理“样式怎么调都不对”的bug,第一件事永远是先看浏览器开发者工具的Elements面板,看DOM结构是否和预期一致。几乎一半的问题,根子都在无效嵌套上。

4.2 属性书写不规范的问题

属性上的坑,除了上面说过的布尔属性,还有几类高频问题。

属性值忘加引号。尤其是像class这种值有多个的,最容易错:

<!-- 错误 --> <div class=card item>内容</div> <!-- 正确 --> <div class="card item">内容</div>

不写引号时,浏览器会把第一个空格当成属性值的结束。上面的错误写法里,card成了class的值,item被当成一个没有值的属性。结果样式和预期完全不一样,而且很难从代码层面直接看出来。

href属性值里的特殊字符。前面提到过&要写成&amp;。另外<和>在属性值里也不能直接用,需要转义成&lt;和&gt;。虽然现在很多人实际这么写了也不报错,但严谨的工程化项目里,这些是会被lint工具的规则拦截的。

alt属性缺失。很多人写图片只写src,不写alt。如果一个用户使用屏幕阅读器访问页面,图片没有alt就是一段空白描述;如果图片因为路径问题加载失败,没有任何替代文字,页面上就是一个裂图图标。这既影响无障碍体验,也不利于SEO。

4.3 注释引发的“灵异事件”

有一种情况排查起来非常头疼,就是注释里包含--导致页面后续代码全部白屏。我给你看一个真实复现场景:

<div>正常显示</div> <!-- 这里是注释,记录了时间范围 2023--2024 --> <div>这条为什么不见了?</div>

你在浏览器里打开,会发现“这条为什么不见了?”这个区块没有显示。因为HTML解析器在遇到<!--之后,会一直往下找第一个-->。注释内容里的--出现在句子中间,导致解析器在更早的位置结束了注释,后面的代码被当作“注释剩余内容”的一部分处理了。

这个问题的可怕之处在于:它不报Javascript错误,也不显示CSS异常,就是页面内容凭空消失。排查方法只有一个——打开Elements面板,看这个元素是被浏览器当成注释节点了,还是真的不存在。经验不够的话,能折腾一晚上。

5. 实战:用今天的内容做一个完整的个人介绍页

5.1 搭建页面骨架

下面这个例子不算复杂,但我特意把今天讲的三块内容全部揉进去了。先看完整代码,再听我逐层拆解。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>个人介绍页</title> <style> .page-header { background-color: #f5f5f5; padding: 20px; } .card { border: 1px solid #ddd; padding: 16px; margin-bottom: 12px; } .tag { display: inline-block; background-color: #eef; padding: 4px 8px; } </style> </head> <body> <!-- ===== 页面头部 start ===== --> <header class="page-header"> <h1>开发者名片</h1> <p>欢迎来到我的个人页面</p> </header> <!-- ===== 页面头部 end ===== --> <!-- ===== 主内容区:这里用 main 标签表示页面核心区域 ===== --> <main class="content"> <!-- 个人信息卡片 --> <section class="card" id="profile">                            
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进