<a> 标签可能是每个前端人写的第一个标签之一。简单到什么程度?<a href="xxx">点这里</a>,三分钟就能学会。

但也正因为太简单了,很多人从第一天写到今天,中间从来没有回头看过一眼。结果就是:能用,但用得不对;能跑,但埋着坑。今天就把这些容易被忽略的细节捋一遍,看看你能中几条。

1. target="_blank" 之后,你少写了一半

新标签页打开链接,这个大家都会:

<a href="https://example.com" target="_blank">去看看</a>

但这行代码其实是不完整的,甚至可以说是有安全隐患的。

原因是这样的:当你用 target="_blank" 打开一个新页面时,新页面通过 window.opener 是可以拿到”打开它的那个页面”的引用的。

也就是说,新页面里的脚本理论上可以反过来操作你原来的页面——比如偷偷把你原来那个标签页的地址改成一个钓鱼网站。这个攻击手法有个专门的名字,叫 “tab nabbing”(标签页劫持)。

正确写法应该带上 rel="noopener noreferrer"

<a href="https://example.com" target="_blank" rel="noopener noreferrer">去看看</a>
  • noopener:切断新页面对 window.opener 的访问,防止上面说的那种攻击。
  • noreferrer:额外阻止浏览器把来源页面的地址(referrer)传给新页面,隐私保护更进一步。

好消息是,现代浏览器(Chrome 88+、Firefox 79+ 等)已经默认给 target="_blank" 加上了隐式的 noopener 行为,安全风险比几年前小了很多。

但这是浏览器帮你兜底,不代表你可以心安理得地不写——老版本浏览器、一些 webview 环境依然不保证这个默认行为,手动加上永远是更稳妥的做法,而且几乎所有的 ESLint 规则(比如 react/jsx-no-target-blank)都会强制要求这么写。

2. href="#"href="javascript:void(0)",你可能一直在滥用

很多人图省事,想要一个”点击执行 JS,但又不跳转”的链接,会这样写:

<a href="#" onclick="doSomething()">点击</a>

或者:

<a href="javascript:void(0)" onclick="doSomething()">点击</a>

这两种写法都有问题。

href="#" 的锚点行为是”跳到页面顶部”或”跳到 id 为空的元素”,点击后页面会有一个跳动感,而且如果你忘了 preventDefault(),浏览器地址栏还会平白无故多出一个 #,手机端还可能触发页面滚动。

href="javascript:void(0)" 则是另一种”伪协议”写法,能避免跳转,但本质上是在滥用 <a> 标签——如果一个元素根本不是用来导航的,它就不该是 <a> 标签

真正该问的问题是:这个交互到底是不是”跳转”?

  • 如果是导航(跳转到另一个页面、锚点、下载文件),用 <a>,给一个真实的 href
  • 如果只是触发一个动作(打开弹窗、提交表单、切换状态),该用的是 <button type="button">
<button type="button" onclick="doSomething()">点击</button>

<button> 天生自带键盘可访问性(可以用 Tab 聚焦、Enter/Space 触发),而给一个 <a> 硬套点击事件,屏幕阅读器用户和键盘用户体验会大打折扣。这也是最容易被 Lighthouse、aXe 这类无障碍检测工具挑出来的问题之一。

3. <a> 里到底能不能嵌套 <div><button>?

HTML5 之后,<a> 标签的语义已经从”行内元素”变成了”可以包裹几乎任何流内容(flow content)的容器”,所以你完全可以写:

<a href="/product/1">
  <img src="cover.jpg" alt="商品图" />
  <div class="title">商品标题</div>
  <div class="price">¥99</div>
</a>

这在电商卡片这类场景里非常常见。但有两个坑要注意:

第一,不能嵌套交互式内容。 <a> 内部不能再放 <button>、另一个 <a><input> 这些”交互式内容”(interactive content),这是 HTML 规范明确禁止的。浏览器虽然大多会”帮你纠错”渲染出来,但结构是不合法的,不同浏览器的容错行为还可能不一致,尤其是嵌套的 <a> 标签,行为完全不可预测。

第二,注意可点击区域的语义污染。 把一整张卡片都塞进 <a> 里虽然点击区域大、体验好,但如果卡片里还有”加入购物车””收藏”之类需要独立点击的按钮,就必须用 event.stopPropagation() 或者干脆把它们放在 <a> 外面,用绝对定位叠上去,否则点”加入购物车”会连带触发跳转。

4. download 属性,你可能一直没用上

想让用户点击链接直接下载文件而不是在浏览器里打开预览?加一个 download 属性就够了:

<a href="/files/report.pdf" download>下载报告</a>

还可以指定下载后的文件名:

<a href="/files/report.pdf" download="2026年度报告.pdf">下载报告</a>

要注意的是,download 属性只在同源资源上生效。如果 href 指向的是跨域的文件地址,浏览器出于安全考虑会忽略 download,直接按正常链接打开——这也是很多人测试时”明明加了 download 却没生效”的真正原因,不是写错了,是跨域了。

5. mailto:tel:,细节比想象中多

<a href="mailto:hello@example.com">联系我们</a>
<a href="tel:+8613800000000">拨打电话</a>

这两个大家基本都会用,但 mailto: 其实还能带更多参数,可以直接预填邮件的主题和正文:

<a href="mailto:hello@example.com?subject=咨询&body=你好,我想了解一下">联系我们</a>

注意参数需要做 URL 编码,中文和特殊符号直接拼接容易出问题。另外 tel: 在桌面浏览器上大多没有实际效果(除非装了对应的拨号软件),所以如果是面向桌面端用户为主的页面,不用太指望它能带来转化,更多是移动端场景的加分项。

6. rel 属性不是只有 noopener 一个值

除了安全相关的 noopenernoreferrer,rel 还有几个和 SEO、内容治理相关的常用值,做内容型网站或者写外链的时候经常会碰到:

  • rel="nofollow":告诉搜索引擎”不要把这个链接的权重传递过去”,常用于不太信任的外部链接、广告链接。
  • rel="sponsored":专门标记付费/广告类链接,Google 在 2019 年后推荐用这个更精确的值替代笼统的 nofollow
  • rel="ugc":标记用户生成内容(评论区、论坛帖子)里的链接,同样是 Google 推荐的细分值。

这几个属性平时写业务代码可能用不上,但如果你的账号或者产品涉及内容运营、外链管理,这就是绕不开的知识点。

7. 无障碍访问:<a> 不只是”能点”

最后聊聊经常被忽视的无障碍(a11y)细节:

空的 href 或者只用 JS 触发跳转的链接,屏幕阅读器读不出意图。 比如:

<a onclick="location.href='/detail'">查看详情</a>

没有 href<a> 标签,在语义上根本不是一个链接,屏幕阅读器不会把它当作可导航元素朗读,键盘用户也无法用 Tab 键聚焦到它。只要是导航,就老老实实给 href 赋值。

图标链接记得加 aria-label 如果一个 <a> 里面只有一个图标(比如社交媒体的图标链接),纯视觉上没问题,但屏幕阅读器读不出这是干什么用的:

<a href="https://twitter.com/xxx" aria-label="访问我们的推特主页">
  <svg><!-- 图标 --></svg>
</a>

链接文案本身要有意义。 一堆”点击这里””查看更多”堆在一个页面里,对屏幕阅读器用户来说是灾难——他们经常会把页面里所有链接拎出来单独听一遍,如果全是”点击这里”,根本分不清哪个是哪个。

尽量让链接文字本身说明去向,比如”查看 2026 年度报告”而不是”点击这里”。

写在最后

<a> 标签看起来简单,但它背后牵扯到安全(tabnabbing)、性能(预加载行为)、SEO(rel 属性)、无障碍(语义和键盘支持)好几个维度。很多问题不是”不会写”,而是”能跑就没深究”。

2026 年了,AI 已经能帮我们写出一行能跑的代码,但这些藏在细节里的”为什么这样写才对”,还是需要我们自己去理解、去把关。毕竟代码审查的时候,AI 不会替你背锅。

下次再写 <a> 标签的时候,不妨多问自己一句:这个链接,是不是真的”该用 <a>“?写得,是不是”经得起推敲”?

© 版权声明

相关文章

暂无评论

none
暂无评论...