1. 从“Expert HTML”这个名字说起它到底想解决什么问题第一次看到“XHTML – Expert HTML”这个标题我脑子里冒出来的第一个念头是这名字起得挺狂。HTML 这门东西从九十年代初一路走到今天几乎成了“人人都会一点”的代名词谁敢自称 Expert但仔细琢磨一下这个“Expert”大概率不是指“专家级语法”而是指“面向专家的 HTML”——也就是说它假设使用者已经过了“怎么加粗、怎么换行”这个阶段真正需要的是对 HTML 这门标记语言的精确控制力和工程化组织能力。这就引出一个很现实的问题我们平时写 HTML到底在哪些地方吃了“不够精确”的亏我列几个我自己踩过的场景你看看有没有共鸣。第一个场景是文档结构失控。一个页面写着写着div套div套了七八层最后自己都找不到某个元素到底在哪一层。CSS 选择器写得越来越长改一个样式要翻半天代码。这不是 HTML 语法的问题是结构组织的问题。第二个场景是语义表达模糊。很多人写页面不管什么内容一律div加 classheader、nav、main、article、aside、footer这些语义标签形同虚设。结果就是搜索引擎看不懂、屏幕阅读器读不明白、团队协作时别人接手你的代码得先猜你的意图。第三个场景是格式与内容的边界混乱。什么时候该用strong什么时候该用bem和i到底有什么区别br换行和p分段在什么场景下该用哪个这些问题看起来小但在大型项目里边界不清就会导致风格分裂。“Expert HTML”这个提法我理解它的核心主张是把 HTML 当成一门需要严肃对待的工程语言而不是随便堆标签的草稿纸。它关注的不是“能不能显示”而是“结构是否清晰、语义是否准确、维护是否方便、协作是否顺畅”。这个定位对于已经能写出能跑的页面、但想让代码质量再上一个台阶的前端从业者来说是非常有价值的切入点。这篇文章我就围绕这个核心主张把 HTML 从“能写”到“写得好”之间那些真正需要经验积累的东西系统地拆一遍。不管你是刚入门前端的新手还是写了几年页面但总觉得代码“差点意思”的老手应该都能从中找到对自己有用的部分。2. 文档骨架的精确控制从 DOCTYPE 到 lang 属性2.1 DOCTYPE 不只是一行声明很多人写 HTML 的第一行!DOCTYPE html是编辑器自动补全的从来没想过它到底在干什么。在“Expert HTML”的视角下这一行是整个文档的模式开关。浏览器解析 HTML 有两种模式标准模式Standards Mode和怪异模式Quirks Mode。!DOCTYPE html这行声明的作用就是告诉浏览器“请用标准模式解析我”。如果不写或者写错浏览器可能进入怪异模式这时候盒模型的计算方式、行内元素的垂直对齐、百分比高度的解析规则都会发生变化。我实测过一个典型案例一个设置了width: 100px; padding: 20px; border: 5px的盒子在标准模式下总宽度是100 40 10 150pxbox-sizing: content-box时但在怪异模式下总宽度就是100pxpadding 和 border 被算进去了。这个差异在布局复杂的时候会引发连锁反应排查起来非常痛苦。所以第一条经验永远确保!DOCTYPE html在第一行前面不能有任何空行、注释或空格。有些构建工具在压缩时会不小心在 DOCTYPE 前插入 BOM 字符这也会触发怪异模式需要在构建配置里注意。2.2 lang 属性的实际作用被严重低估html langzh-cn这个属性我观察下来大部分人是复制粘贴模板时顺手带上的根本没想过它的实际作用。但它的影响面其实很广屏幕阅读器会根据lang值选择正确的发音引擎。如果设成en但内容是中文读屏软件会用英语发音规则读中文听起来非常奇怪。浏览器翻译功能会参考lang判断是否需要弹出翻译提示。设对了浏览器知道这是中文页面不会多此一举。CSS 的:lang()伪类可以基于这个属性做样式区分比如中英文混排时给不同语言设置不同字体。搜索引擎用它来判断页面的目标语言区域影响搜索结果的地区匹配。lang值的写法有讲究zh-cn表示简体中文中国大陆zh-tw表示繁体中文台湾地区zh-hk表示繁体中文香港地区en表示英语ja表示日语。格式是“语言代码-国家/地区代码”语言代码用小写地区代码用大写或小写都可以但习惯上地区代码大写更规范。注意如果页面主体是中文但夹杂了英文段落可以在英文段落上单独加langen这样屏幕阅读器切换发音规则体验会好很多。2.3 meta charset 的位置和时机meta charsetutf-8这行必须放在head的最前面最好在title之前。原因是浏览器在解析 HTML 时需要先知道字符编码才能正确解码后续内容。如果这行放得太靠后浏览器可能已经用默认编码通常是 ISO-8859-1 或系统默认解析了一部分内容导致中文乱码。我遇到过一种情况页面在本地打开正常部署到服务器后中文全变问号。排查半天发现是服务器返回的 HTTP 头里Content-Type指定的编码和 HTML 里的meta charset不一致浏览器优先采用了 HTTP 头的编码。所以最佳实践是服务器端和 HTML 端都明确指定 UTF-8两边保持一致。2.4 viewport 与移动端适配的底层逻辑meta nameviewport contentwidthdevice-width, initial-scale1.0这行在移动端开发里几乎是标配但它的工作原理值得说清楚。在没有这行的情况下移动端浏览器会默认用一个大约 980px 的虚拟视口来渲染页面然后缩小到屏幕宽度显示。结果就是字体变得很小用户需要双指放大才能看清。加上这行之后视口宽度等于设备物理宽度initial-scale1.0表示初始缩放比例为 1页面按实际尺寸渲染。viewport还有几个常用参数maximum-scale限制最大缩放比例user-scalableno禁止用户手动缩放。但从可访问性角度我不建议禁用用户缩放因为视力不好的用户需要放大来看内容。除非是特定的全屏应用场景否则保留缩放能力是更负责任的做法。3. 语义化标签的实战选择什么时候用什么3.1 语义化的真正价值不在“好看”很多人对语义化的理解停留在“用header比用div classheader更专业”这个层面。但语义化的真正价值体现在三个地方第一是可访问性。屏幕阅读器用户可以通过语义标签快速跳转到页面的不同区域。比如nav会被识别为导航区域main会被识别为主内容区用户可以用快捷键在这些区域之间跳转。如果全用div屏幕阅读器用户只能从头到尾线性地听完整页内容效率极低。第二是搜索引擎理解。搜索引擎爬虫会分析页面的语义结构判断哪部分是主要内容、哪部分是辅助信息。article里的内容会被认为比aside里的内容更重要这直接影响搜索排名。第三是团队协作效率。当你的同事看到aside时他立刻知道这是侧边栏相关内容看到article时他知道这是一篇独立的、可以单独分发的内容单元。这种“代码即文档”的效果比写一堆注释管用得多。3.2 一张表说清常用语义标签的适用边界标签适用场景不适用场景常见误用header页面或区块的头部包含标题、logo、导航页面主体内容把整个页面顶部区域都塞进去包括不该有的内容nav主要导航链接组页面底部的版权链接、社交分享按钮页面上所有链接组都加 navmain页面唯一的核心内容区多个 main 并存每个区块都包一个 mainarticle独立可分发的完整内容单元评论区里的单条评论除非评论本身可独立分发把整个页面包成一个 articlesection有主题的内容分组通常带标题纯样式容器用 section 代替 div 做布局aside与主内容相关但可独立存在的补充内容主内容的一部分把侧边栏所有内容都塞进一个 asidefooter页面或区块的底部包含版权、联系方式页面主体内容和 header 一样范围界定不清这张表里的“常见误用”一栏是我在代码审查中见得最多的问题。特别是section和div的混用很多人觉得section更“语义化”就到处用结果一个页面里十几个section每个里面就一两行内容反而让结构更混乱。我的判断标准很简单如果一个内容块需要一个标题来表明它的主题用section如果只是纯粹为了布局或样式分组用div。3.3strongvsb、emvsi的取舍这四组标签的视觉表现是一样的strong和b都是粗体em和i都是斜体但语义完全不同。strong表示重要性b表示视觉上的突出但不强调重要性。举个例子在一篇产品说明里“本产品不支持退货”这句话里的“不支持”应该用strong因为它传达了关键信息而产品名称“超级编辑器”如果只是想让它视觉上突出用b就够了。em表示强调重音i表示不同于周围文字的语气或类别。比如“我真的没说过这句话”里的“真的”用em表示语气重音而外来词、技术术语、内心独白用i。实际项目中我的建议是优先用strong和em只在纯视觉需求且无语义含义时用b和i。这样屏幕阅读器在朗读时遇到strong和em会加重语气遇到b和i则正常朗读信息层次更清晰。3.4 列表标签的选择逻辑ul、ol、dl这三个列表标签选择逻辑其实很清晰ul无序列表用于一组没有先后顺序关系的项目。导航菜单、功能特性列表、标签云都用它。ol有序列表用于有明确顺序关系的项目。操作步骤、排行榜、法律条款用它。dl描述列表用于术语和定义的配对。词汇表、FAQ、元数据展示用它。dl的用法值得单独说一下。它由dt描述术语和dd描述详情组成一个dt可以对应多个dd。比如dl dtHTML/dt dd超文本标记语言用于构建网页结构/dd dtCSS/dt dd层叠样式表用于控制网页外观/dd dd可以独立于 HTML 文件存在/dd /dl这种结构在展示键值对数据时非常合适比用div加 class 模拟要清晰得多。4. 表单HTML 里最容易被写烂的部分4.1 label 与 input 的绑定方式表单可访问性的第一道坎就是label和input的绑定。有两种绑定方式方式一包裹式label 用户名 input typetext nameusername /label方式二for/id 关联式label forusername用户名/label input typetext idusername nameusername两种方式效果一样点击 label 文字都能聚焦到对应的输入框。但方式二更灵活label 和 input 可以分开布局。需要注意的是for属性的值必须和 input 的id完全一致大小写敏感。我见过很多表单里 label 和 input 完全没有绑定关系用户点击“用户名”三个字没有任何反应必须精确点击输入框才能输入。这在移动端尤其影响体验因为手指点击区域本来就小。4.2 input type 的正确选择HTML5 引入了大量新的 input type但实际使用率很低。很多人不管什么输入一律typetext白白浪费了浏览器提供的原生能力。type 值适用场景浏览器增强行为email邮箱地址移动端弹出带 的键盘自动校验格式tel电话号码移动端弹出数字键盘url网址移动端弹出带 / 和 .com 的键盘number数值显示上下调节箭头限制只能输入数字date日期弹出日期选择器search搜索框输入时显示清除按钮password密码输入内容显示为圆点用对 type 的好处是显而易见的移动端用户不用在字母键盘和数字键盘之间来回切换输入效率大幅提升浏览器自带的格式校验可以在提交前就发现问题减少无效请求。4.3 表单验证的 HTML 原生方案在 JavaScript 验证大行其道的今天很多人忽略了 HTML 本身就提供了相当完善的验证能力。required、pattern、min、max、minlength、maxlength这些属性配合 CSS 的:valid和:invalid伪类可以做出体验很好的验证效果。input typetext namephone required pattern1[3-9]\d{9} title请输入11位手机号码 placeholder请输入手机号 这段代码实现了必填、必须是1开头的11位数字、不满足时显示自定义提示。浏览器会在提交时自动拦截不合规的输入并显示提示信息。注意pattern属性默认是全匹配也就是说整个输入值必须完全符合正则表达式。不需要在正则前后加^和$浏览器会自动处理。原生验证的局限在于提示样式无法自定义各浏览器表现不一且验证时机比较固定通常在提交时触发。对于体验要求高的项目可以用novalidate属性关闭原生验证改用 JavaScript 实现自定义验证逻辑。但即便如此保留 HTML 验证属性作为“兜底”也是好习惯——万一 JavaScript 加载失败表单至少还有基本校验。4.4 表单分组与 fieldset当一个表单内容较多时用fieldset和legend进行分组可以显著提升可读性和可访问性。fieldset legend收货信息/legend label forname姓名/label input typetext idname namename label foraddress地址/label input typetext idaddress nameaddress /fieldsetfieldset会在视觉上形成一个分组框legend是分组标题。屏幕阅读器在进入每个分组时会朗读 legend 内容让用户知道当前在填写哪部分信息。这个细节在长表单里对可访问性提升非常明显。5. 从“能跑”到“好维护”HTML 工程化的几个关键习惯5.1 属性顺序的约定HTML 属性的书写顺序不影响功能但影响可读性和团队协作效率。我推荐一个固定的属性顺序让代码审查和后续维护更顺畅class最常用于样式和 JS 选择id唯一标识name表单元素>table caption2024年第一季度销售数据/caption thead tr th scopecol产品/th th scopecol销量/th th scopecol增长率/th /tr /thead tbody tr th scoperow产品A/th td1200/td td15%/td /tr /tbody /tablecaption是表格标题th的scope属性告诉屏幕阅读器这个表头是管列的还是管行的。这些细节在数据密集型页面里对可访问性影响很大。6.4 字符实体的使用时机lt;、gt;、amp;、quot;、copy;这些字符实体什么时候该用我的原则是和在作为文本内容时必须转义否则浏览器会当成标签开始/结束在作为文本内容时建议转义虽然浏览器通常能容错处理但转义更安全引号在属性值里如果和属性值的引号冲突需要转义版权符号、商标符号等特殊字符用实体比直接输入更可靠避免编码问题现代编辑器大多能自动处理这些转义但理解背后的原因在排查问题时很有帮助。7. 写在最后HTML 的“Expert”之路没有终点写了这么多年 HTML我越来越觉得这门语言的“简单”是一种假象。语法确实简单标签数量有限入门门槛极低。但要把 HTML 写好、写精、写到别人接手时挑不出毛病需要的是对细节的持续关注和对规范的敬畏。“Expert HTML”这个提法我理解它想传达的是一种态度不满足于“能显示”追求“结构清晰、语义准确、可访问、易维护”。这条路没有终点因为规范在演进浏览器在更新用户的需求在变化。但每往前走一步你的代码质量就上一个台阶你和其他开发者的差距就拉开一点。我个人的习惯是每隔一段时间回头看看自己半年前写的 HTML如果觉得“这写的什么玩意儿”说明进步了。如果觉得“还挺好”那可能这段时间没什么长进。保持这种自我审视比学任何具体技巧都重要。