老老实实把 HTML 吃透,比什么花活都管用
干了这么多年前端,我带过的实习生、校招生不下两位数。很多人一上来就问我“框架怎么学”“项目用什么架构”,但真到排查问题的时候,连一个标签该不该嵌套、一个属性放在哪里生效都说不清楚。
我给他们的建议永远是一句话:先把 HTML 啃明白。这不是客套,HTML 不是“几个标签而已”,它背后是浏览器渲染机制、SEO 抓取逻辑、可访问性规范、性能优化底线,甚至表单安全策略。这篇文章就是我这些年踩坑、填坑、看别人埋坑之后整理出来的 HTML 知识点梳理,偏实战、重原理,适合准备入行的新手,也适合写了几年 CSS/JS 但 HTML 基础不牢的开发者对照查漏。
1. 语义化不是加分项,而是基本功
1.1 为什么语义化标签比 div 堆砌更值钱
很多新手觉得页面能用就行,header、footer、main、aside 这些标签“多此一举”,div 加个 class 完全能替代。这个观点大错特错。
语义化标签解决的问题不是“好看”,而是“可理解”。浏览器、搜索引擎爬虫、屏幕阅读器都依赖标签本身携带的含义去解析页面结构。你用 div 拼出来的页面对机器来说就是一堆没有层级关系的盒子,而用语义标签写出来的页面,机器能清楚知道哪里是导航、哪里是主体内容、哪里是补充信息。
举个实际例子,我在做某个资讯类站点改版时,发现搜索收录一直不理想。后来排查发现,整个页面的正文区域居然用的是 div 嵌套,没有 article、没有 h1 层级,连段落都是 p 标签套 span 硬撑的。调整之后,核心关键词的收录速度明显改善,这不是玄学,是爬虫真的读懂了页面。
1.2 标题层级、列表与导航标签的规范用法
h1 到 h6 的层级不只是字号差异,它代表文档大纲。一个页面原则上只应该有一个 h1,通常是 logo 或页面主标题,然后按内容层级依次使用 h2、h3。很多后台管理系统会把“欢迎您,某某某”这种提示也写成 h1,这是典型的滥用。
ul、ol、dl 三种列表各有用途。无序列表用于并列信息,有序列表用于有先后顺序的步骤,描述列表用于术语与解释的成对出现。实际开发中,导航菜单就是 ul 嵌套 li 的标准场景,而不是一堆 div 加 a 标签拼出来的“假列表”。
nav 标签不仅用于顶部主导航,页脚的法律信息链接、侧边栏的目录跳转,只要是“一组导航性质的链接集合”,都建议用 nav 包起来。但要注意,nav 不要滥用,普通的分页器、单个的“返回首页”链接,用 nav 反而增加了解析负担。
1.3 移动端优先与响应式的基础容器
HTML 层面的移动端适配,最核心的是 viewport 设置。很多人复制模板时只记得<meta name="viewport" content="width=device-width, initial-scale=1.0">,但并不知道它到底在干什么。没有这行声明,手机浏览器会默认用 980px 左右的虚拟视口渲染页面,然后整体缩小,文字小到看不清,用户必须手动放大才能阅读。
另外,图片和表格在移动端的溢出问题,根源往往不在 CSS,而在 HTML 结构。图片没有设置 max-width: 100%,表格没有包裹在可横向滚动的容器里,都是因为写 HTML 的时候没考虑内容会“撑破”屏幕。最根本的习惯是,任何媒体内容放进页面时就要有“移动端会如何展示”的预判,而不是等样式出问题了再回头补结构。
2. 文档结构、head 区与资源加载的细节
2.1 head 里最容易写错和漏掉的 meta 标签
head 区是很多人的盲区,因为它在页面上不显示任何东西,但它的重要性完全不亚于 body 里的内容。
第一个是 description。它的作用是向搜索引擎简要描述页面内容,搜索结果里那句灰色小字就是它。不写的话搜索引擎会自己截取,结果经常是页面里最无关紧要的一行文字。而且 description 不要每次都写一样,每个页面最好有独立的描述,包含该页面的核心关键词。
第二个是 canonical 标签。这个标签用来告诉搜索引擎“这个页面的标准地址是哪个”。尤其是电商、博客这类系统,同一篇文章可能通过多个 URL 访问,如果不用 canonical 指定权威地址,很容易被判定为重复内容,导致权重分散。
第三个是 og 系列标签,也就是 Open Graph 协议。它的作用是控制在社交平台分享时展示的标题、描述、图片。不做 og 标签的页面分享到聊天软件里就是干巴巴一条链接,做了之后是一张卡片。这个细节很多开发团队完全不重视,但内容运营团队非常依赖它。
2.2 图片、脚本与样式的加载顺序
HTML 的解析是自上而下的,遇到同步脚本会阻塞渲染,这个机制决定了很多性能优化的思路。
核心原则是:CSS 放 head,JS 放 body 底部或使用 defer。CSS 放 head 是为了让浏览器尽早拿到样式,避免页面先渲染出无样式内容(FOUC);JS 放底部是为了让 DOM 先解析完,防止脚本找不到元素或阻塞首屏。
现代浏览器对脚本提供了 defer 和 async 两种异步加载方式。defer 是在 HTML 解析完成后按顺序执行,适合有依赖关系的脚本;async 是下载完立即执行,不保证顺序,适合独立无依赖的第三方脚本。实操中,业务代码用 defer 更稳,数据统计类的埋点脚本用 async 就可以。
还有一点经常被忽略:preload 与 prefetch。preload 用于提前加载当前页面必然要用到的关键资源,prefetch 用于空闲时预取用户下一步可能访问的页面资源。这两个标签需要开发者对用户行为有预判,不是无脑加越多越好,加多了反而浪费带宽。
2.3 一个标准的 HTML5 骨架包含什么
我见过太多项目里躺着十几年前的老旧模板,有的连<!DOCTYPE html>都写得不规范。一个干净、完整、现代的 HTML5 骨架应该包含:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="页面描述"> <title>页面标题</title> <link rel="stylesheet" href="style.css"> <script src="app.js" defer></script> </head> <body> <header>...</header> <main>...</main> <footer>...</footer> </body> </html>lang 属性很多人不写或者写错。它影响屏幕阅读器的发音、浏览器翻译的触发、搜索引擎对页面语言的理解。一个中文站点的 lang 应该是zh-CN,而不是默认忘记写或写成en。
title 是 SEO 的重要元素,也往往是用户打开多个标签页时区分页面的唯一依据。每个页面都应该有独立的、包含关键词的 title,而不是全站统一写“首页”。
3. 文本、超链接与图片标签的进阶玩法
3.1 文本标签里那些不常注意的细节点
strong 和 b、em 和 i 的区别,面试常问,但实际写代码时很多人还是混用。strong 表示内容重要性,应该是“重要”而不是“加粗”;b 只是视觉上的粗体,不带语义。em 表示语气强调,i 只是斜体视觉样式。视觉上它们可能一模一样,但对屏幕阅读器和搜索引擎来说完全不同。
br 标签的使用要克制。它是强制换行,而不是用来制造段落间距的。很多人图省事,在文本中间插一排 br 做留白,这种做法在响应式布局里很容易崩,因为不同屏幕宽度下换行位置根本不可控。留白应该交给 CSS 的 margin 和 padding。
小细节:<abbr>标签用于缩写词,配合 title 属性可以给出全称,鼠标悬停会显示提示。这在科技类、医学类等大量使用专业缩写的业务里尤其有用。
3.2 a 标签的 target、rel 与下载属性
target="_blank" 在新窗口打开链接,这是常识。但很多人不知道,加了 target="_blank" 之后,被打开的页面可以通过 window.opener 对象反向操作原页面。这在安全上是个隐患,所以现代开发规范要求所有 target="_blank" 的链接同时加上 rel="noopener noreferrer"。现在的浏览器默认行为已经有所收敛,但规范还是要写。
下载属性 download 可以让链接直接触发下载而不是打开文件,但要注意,它只在同源 URL 下生效,跨域 URL 会被浏览器忽略。所以跨域文件的下载,后端给出的响应头里需要带 Content-Disposition: attachment 才行。
另外,锚点跳转不要只知道href="#top"。给目标元素设置 id,然后链接指向#目标id,这才是正路。还可以用scroll-behavior: smooth做成平滑滚动,不需要 JS 插件。
3.3 图片标签的完整参数:alt、width、height 与懒加载
alt 属性的作用有两个:一是图片加载失败时显示替代文本,二是屏幕阅读器会读出它来帮助视障用户理解内容。SEO 方面,搜索引擎也把它当关键词之一。但 alt 不能堆砌关键词,描述清楚图片内容即可。纯装饰性图片,alt 直接留空字符串(alt=""),让屏幕阅读器跳过,反而更友好。
width 和 height 属性很多人以为过时了,其实并不是。在 HTML 里给图片预设宽高可以避免布局偏移(CLS),这是 Core Web Vitals 指标之一。不给宽高的图片在加载过程中会把页面撑得上下跳动,用户阅读体验非常差。
现代浏览器支持原生懒加载,只需要一行属性 loading="lazy"。图片进入视口附近时才加载,从而节省流量、提升首屏速度。但要注意,首屏内的图片不能加 lazy,否则会影响 LCP(最大内容绘制)指标。首图必须立即加载。
4. 表单、数据提交与前端校验的边界
4.1 input 类型与各自适用的场景
HTML5 给 input 增加了大量新类型,很多人在移动端项目里还只会用 text,这太浪费了。
type="email" 在移动端会弹出带 @ 符号的键盘;type="tel" 会弹出数字键盘;type="url" 会弹出带斜杠的键盘;type="number" 在移动端弹出数字键盘,但在桌面上依然允许输入 e、+、- 等字符,如果业务上只需要整数,建议配合 min、max、step 一起使用。
更实用的是 type="date" 和 type="datetime-local"。前者在移动端直接弹出原生日期选择器,用户在 iOS 和 Android 上体验都很好。但桌面端 Safari 支持并不理想,如果你要兼容老 Safari,可能需要引入第三方日期组件。
还有一个容易被忽略的点:type="file" 的 accept 属性。它用于限定可以选择文件的类型,比如 accept="image/*" 表示只允许选择图片。这里的类型是 MIME 类型,不是文件扩展名。实际上如果设置 accept=".jpg,.png" 也可以,但兼容性不如 MIME 类型写法稳定。
4.2 必填、正则验证与错误提示的 HTML 实现
required 属性可以让表单在提交时自动检查该项是否为空,不需要写任何 JS。配合 CSS 的:required伪类,可以给必填项加视觉标识。
pattern 属性直接写正则表达式,匹配失败时浏览器会阻止提交并用默认文案提示。比如 pattern="\d{11}" 可以校验 11 位手机号。要注意的是,pattern 匹配的是整个输入值的完整正则,不是“包含匹配”,所以大多数情况下需要自己加^和$锚点,或者直接写完整的匹配规则。
maxlength 限定最大长度,minlength 限定最小长度。表单在提交时浏览器会自动检查。这两个属性在 input 和 textarea 上都生效。
错误提示的默认样式虽然丑,但它免费、可靠、跨端一致。很多时候业务上不需要定制提示文案,用默认的就够了。如果确实需要自定义,可以通过setCustomValidity()方法在 JS 里设置提示语,但要注意,设置之后必须清空,否则表单会一直处于不可提交状态。
4.3 form 的 action、method 与数据序列化
form 的 method 默认是 GET,action 不写就直接提交到当前 URL。很多人写 AJAX 交互写习惯了,反而忘了原生表单的提交逻辑。
GET 提交会把数据拼在 URL 查询字符串上,适用于搜索、筛选这类无状态请求。POST 提交的数据在请求体里,适合新增、修改、删除这类有副作用的操作。在原生表单场景里,method 写错最典型的后果是:URL 参数混乱、请求被缓存、日志记录里出现敏感信息。
form 内所有带 name 属性的表单控件都会被序列化提交。如果你想让某些字段不发往后端,不要给它 name 属性,或者直接设为 disabled。但要注意,disabled 的控件不会提交,但不会隐藏,配合 hidden 可以做到“界面不可编辑但也不再提交”。真正想提交又不展示的字段,用<input type="hidden" name="xxx" value="yyy">。
还有一个现代浏览器的新特性:form 的 requestSubmit() 方法可以用 JS 触发原生的表单校验流程,而 submit() 方法不会触发校验。两者差别很细微,但排查“为什么表单没校验就提交了”的时候,十有八九是你用了 submit()。
5. 可访问性(A11Y)在 HTML 层面的落地
5.1 WAI-ARIA 角色的核心价值
WAI-ARIA 是写给辅助技术(主要是屏幕阅读器)的属性体系,它的作用是弥补原生 HTML 语义不足的问题。
最简单的例子:一个用 div 做的模态框。div 本身没有“这是对话框”的语义,屏幕阅读器用户不知道它是什么。这时候给 div 加上 role="dialog",再加 aria-modal="true" 和 aria-labelledby 指向标题元素,屏幕阅读器就能正确识别并播报。
还有 aria-label 和 aria-labelledby。前者直接在属性里写一段描述文字,后者引用页面上已有的元素 id 来作为名称。比如一个搜索按钮,内容是图标没有文字,视觉上能理解,但屏幕阅读器读不出来。给它加 aria-label="搜索",问题就解决了。
ARIA 的使用要克制。优先使用原生语义标签,只有原生标签满足不了时才上 ARIA。无脑加 role 反而会干扰辅助工具的解析。
5.2 焦点管理与键盘操作顺序
可访问性不只面向盲人用户,也面向无法使用鼠标的用户。很多键盘用户靠 Tab 键在页面元素间跳转,焦点顺序就是 DOM 顺序,这一点要记住。
常见的问题是:弹出层打开后,焦点还停留在背后的按钮上,键盘用户根本不知道弹窗出现了。正确做法是打开弹窗时把焦点移入弹窗,关闭时再回到触发按钮。这需要 JS 配合操作,但 HTML 结构上要把弹窗放在页面中的合理位置,让 Tab 顺序符合视觉顺序。
skip link(跳转链接)也是常见做法。它是个视觉上隐藏、键盘操作时才可见的链接,放在页面最顶部,作用是跳过导航直接跳到主内容。代码上就是一个普通 a 标签:<a href="#main-content" class="skip-link">跳到正文</a>。
5.3 form 控件与 label 绑定的正确姿势
label 与表单控件的绑定,最直接的方式是用 for 属性指向控件的 id。点击 label 文字时,浏览器会自动聚焦到对应控件。这是一个很小的细节,但使用率极低,很多团队的表单代码根本没有 label 标签,或者 label 没有写 for。
还有一种写法是隐式关联,把控件嵌套在 label 里面,但隐式关联在某些辅助工具与旧浏览器上的支持不稳定。实操建议一律用显式关联(for + id),排除兼容性隐患。
radio、checkbox 这类选择型控件,如果没绑定 label,点击命中区域非常小,移动端上尤其难操作。把 label 做全,命中区域变大,用户误触率直接下降。