1. 一个被写错的属性名,让我重新审视了a标签
先讲个真事儿。上周帮一个前端群里的朋友排查问题,他写的链接死活不跳转,代码如下:
<a herf="https://example.com">点击跳转</a>我一眼就看到了问题:herf拼错了。标准写法是href,全称叫Hypertext Reference,超文本引用。这个拼写错误导致的直接后果是浏览器根本不认识这个属性,自然不会执行跳转。
让我印象更深的不是这个错误本身,而是聊天记录里还有一条更有意思的对话。一个自称"写过三年页面"的人说:a标签不就一个href加一个target,还有什么好讲的?
恰恰是这个想法,让很多人在处理相对路径、锚点定位、页面拦截、新窗口安全这些实际问题时栽了跟头。a标签是两个属性能搞定的事情,但这两个属性的每一种取值组合,对应的是浏览器几十种不同的处理逻辑。细节不在标签本身,而在标签背后的行为。
这篇文章我不打算写一份属性API文档,而是从一个实际开发者的视角,把href和target的用法拆开揉碎,讲清楚每一个取值背后浏览器到底做了什么、什么场景该用什么写法、以及那些踩过才记得住的坑。
2. href的每一种取值背后都是一种资源定位方式
2.1 绝对路径和相对路径的前世今生
href最基础的用法是填一个地址,但这个地址写多细、从哪儿开始写,很多人没认真想过。
绝对路径带着完整协议,形如https://example.com/page.html,浏览器拿到后会直接发起DNS解析、建立连接、请求资源。它适合站外链接、带参数的接口地址、以及需要保证任何页面跳转结果一致的场景。
相对路径则不写协议和域名,它依赖当前页面的URL来计算最终地址。这恰恰是初学者最容易出问题的地方。
相对路径有两种起始写法:
<!-- 以/开头,从域名根目录找资源 --> <a href="/about.html">关于我们</a> <!-- 不带/,从当前目录找资源 --> <a href="about.html">关于我们</a>/about.html无论你在/products/list.html还是/about.html页面,最终都会跳到域名下的about.html。而about.html则不同,它在/products/list.html页面点击时,浏览器会拼成/products/about.html。这个细节不搞清楚,经常出现"本地预览正常、上线后全部404"的诡异问题。
2.2 通信类协议:mailto、tel和下载链接
href能填的不只是网页地址,它还能唤起本地应用。
<a href="mailto:support@example.com">发邮件</a> <a href="tel:400-888-9999">拨打电话</a>mailto:会打开系统默认邮件客户端,还能在地址后带参数预设收件人、主题和内容:
<a href="mailto:support@example.com?subject=问题反馈&body=你好,我遇到了一个问题">联系技术支持</a>?后面是查询参数,多个参数用&连接,相当于替用户把邮件标题和正文都填好了。移动端页面里这个用法非常实用,用户点了直接把邮件应用唤醒,填了内容就能发。
tel:则是移动端的神器,手机上点了直接进入拨号界面,不需要用户手动复制号码。有用户反馈说"我点了没反应",十有八九是因为在PC上测试,桌面浏览器对tel:的支持逻辑是调用本地通讯软件,有的系统没装就直接忽略。
还有一类是文件下载链接。服务器如果返回了带有Content-Disposition: attachment响应头的文件,浏览器就会直接下载不预览。但在前端层面,给文件直链加上download属性,也能让浏览器把资源当附件下载而不是打开预览:
<a href="/files/report.pdf" download>下载报告</a>download还可以填写另一个文件名,告诉浏览器另存为时使用什么名字。不过注意,download属性只在同源URL下生效,跨域文件它管不了,这是浏览器的安全策略。我试过跨域加download属性,Chrome直接无视,照样打开预览窗口。
2.3 锚点:回到历史深处的跳转方式
锚点用href="#id"实现,点击后页面滚动到对应id的元素位置,浏览器地址栏会自动加上#xxx。
<a href="#section-3">跳转到第三节</a> <div id="section-3"> <h2>第三节内容</h2> </div>这个用法看起来简单,但有个细节值得说。锚点跳转默认是瞬移,没有平滑动画。想要滚动过渡效果,要么用CSS:
html { scroll-behavior: smooth; }要么用JS的scrollIntoView方法。还有一点容易被忽略:页面里有多个相同id元素时,锚点会跳到文档中第一个匹配的节点。把id当类名一样复用,是很多人踩过的坑。
锚点还能跨页面跳转到指定位置:
<a href="/guide.html#faq">去帮助页看FAQ</a>这样跳过去的页面会自动定位到id="faq"的位置。多页站里的文档中心、FAQ页面很适合用这种方式做目录引导。
2.4 javascript:伪协议,一个危险又没法完全躲开的写法
href="javascript:"这种写法放在今天会有争议,因为从安全角度说,直接在HTML属性里塞可执行代码属于内联脚本,现代站点通常会被CSP(内容安全策略)拦截。但老代码里确实大量存在,前端的面试里也还偶尔会问。
它的本质是浏览器遇到javascript:协议时,停止页面跳转,把后面的字符串当JavaScript代码执行:
<a href="javascript:void(0);" onclick="submitForm()">提交</a>void(0)是为了让表达式返回undefined,防止页面被替换成返回值。这个写法的意图就是:点击时不跳转、只执行脚本。
今天再做这个需求,更推荐改成按钮或改成拦截默认行为:
<a href="#" id="submit-link">提交</a> <script> document.getElementById('submit-link').addEventListener('click', function (e) { e.preventDefault(); // 阻止默认跳转行为 submitForm(); }); </script>或者干脆上<button>元素,语义上更准确。
3. target属性:浏览器如何决定页面的打开方式
3.1 五个内置取值的行为差异
target的全称叫browsing context,翻译过来是浏览上下文,指向的是"在哪里打开链接"。它有四个固定的内置值:
| target值 | 行为 |
|---|---|
_self | 默认值,在当前页面打开(前提是当前页面不在iframe里) |
_blank | 新开一个标签页或窗口打开 |
_parent | 在父级浏览上下文打开(主要用于iframe嵌套) |
_top | 在顶层上下文打开,跳出所有iframe |
显式写上target="_self"和不写效果一样,但有一种场景它有用:如果页面有一个<base target="_blank">,你希望某个链接在当前页打开,那就必须显式声明_self来覆盖base标签的全局设置。
_parent和_top是iframe时代的产物。页面被嵌在别人网站的iframe里时,_top可以让链接强制在整个浏览器标签页打开,而不是在iframe的小框里跳转。_parent只向上跳一层,如果只有一层iframe,_parent和_top效果一样。
3.2 自定义窗口名:一个被遗忘的老技术
除了四个内置值,target还可以填一个自定义名字。浏览器会维护一个窗口名登记表,当目标名字不存在时,就新建一个窗口/标签页,然后把这个名字分配给新窗口。
<a href="/login" target="authWindow">登录</a> <a href="/register" target="authWindow">注册</a>第一次点"登录",浏览器会新开一个标签页;点完登录再点"注册",如果authWindow这个名字的窗口还在,就不会再新开标签,而是直接在那个窗口里加载注册页。
如果那个标签页被人手动关掉了,再点就又会新建一个。这个特性在某些单点登录流程里可以拿来用。但说实话,现在的前端开发里自定义窗口名用得不多,知道有这回事儿、遇到时能识别就行。
3.3 base标签的全局target覆盖
<base>标签常放在<head>里,它有两个作用:一是给页面里所有相对路径设置默认基地址,二是设置所有链接的默认target。
<head> <base href="https://example.com/" target="_blank"> </head>这段代码会让页面里所有不带协议的相对链接都以https://example.com/为基准解析,所有链接默认新窗口打开。只要某个a标签没有显式写target,就默认继承_blank。
这种写法省事,但隐患也不小。全站所有外链、内链全部新窗口打开,用户开了十几个标签页,体验反而很糟。我的建议是就算要用base设置target,最好也只配合相对路径使用,并且刻意在需要同页跳转的链接上单独覆盖回_self。因为base标签的影响面是全局的,排查问题时极其隐蔽——你不知道什么时候多了个base标签,结果所有链接行为都变了。
3.4 _blank带来的那个安全漏洞
target="_blank"有个著名的安全隐患,叫tabnabbing(标签页钓鱼)。原理是:当页面A用_blank打开新页面B后,A的window.opener对象仍持有对新页面B的引用。反过来,B的JavaScript在某种条件下也可以操纵A的回退地址或内容。
老版本浏览器里恶意页面可以这样攻击来源页:
// 这在老浏览器里可以执行,针对opener页面进行钓鱼替换 window.opener.location = 'https://phishing-site.com/fake-login';用户切回原标签页时看到的是一个伪造的登录页,以为是自己之前打开的网站,输入了账号密码,结果被钓鱼了。
现代浏览器已经默认给target="_blank"附加了noopener语义,但历史代码和低版本浏览器如果你还在支持,就有了遗留风险。所以标准做法是在所有target="_blank"的链接上补上rel:
<a href="https://外部站点.com" target="_blank" rel="noopener noreferrer">外部链接</a>noopener:不让新页面获得window.openernoreferrer:不发送来源信息。它会隐式启用noopener效果
现在写rel="noopener noreferrer"更多的是一种防御性写法,新浏览器下就算只写noopener也够了,两个都写兼容最稳妥。
4. 点击之外的分支:当a标签遇到键盘、SEO和样式细节
4.1 没有href的a标签还是链接吗
HTML里a标签的href属性不是必填的。但一旦不填href,这个元素就失去了"可交互链接"的特性——没有下划线、鼠标样式不变、Tab键焦点顺序里默认也不包含它、屏幕阅读器不会把它识别为链接。
常见场景是页面里的"按钮式链接"或Tab页切换,这类a标签只有onclick事件,href要么不写要么写成#。从可访问性角度,这种实现是有问题的。键盘用户没办法通过Tab聚焦到它,辅助技术也无法告知用户"这是一个可点击的链接"。
补上href后,元素重新进入目光焦点序列,浏览器也恢复了默认的交互样式。如果又不想真的跳转,就用JS拦截preventDefault()。这个做法让"看起来像链接"的东西真的是链接。
4.2 一个href属性里的URL编码细节
a标签内的URL包含中文、空格、特殊字符时,浏览器会自动编码,但强烈建议手动编码而不是依赖浏览器容错。尤其是有参数字符串时:
<!-- 不推荐,遇到特殊字符容易被截断或报错 --> <a href="/search?keyword=前端 入门">搜索</a> <!-- 推荐,先编码 --> <a href="/search?keyword=%E5%89%8D%E7%AB%AF%20%E5%85%A5%E9%97%A8">搜索</a>编码工作可以用encodeURIComponent统一处理。没做编码的链接,遇到&解析参数时会被分割,#又会被当成锚点。你写的是keyword=苹果&5,浏览器认为后面的5是另一个参数,直接吞掉。
4.3 响应式场景下的href陷阱
在移动端触屏设备上,有一个传统PC没有的特有问题:hover伪类。手机上第一次点击触摸目标时,浏览器会先触发hover状态、再等待后续手势。如果a标签本身有较复杂的hover效果或者跳转前绑定了mouseenter事件,就会出现"点一下没反应,再点一下才跳转"的延迟。
这不是href写错的问题,而是事件绑定和样式实现方式造成的。通用解决方案是在触摸设备上关掉hover效果:
@media (hover: none) { a:hover { background: inherit; } }这和target本身没关系,但放在一起说是因为:排查"为什么移动端链接点击不跳转"时,第一步先分清是href的问题还是事件绑定的问题。
4.4 站内SEO的链接处理
从SEO角度,a标签的锚文本会被搜索引擎当作目标页面的描述信息。不要写"点击这里",NNN个内部链接全叫"点击这里",搜索引擎无法判断链接之间的主题差异。写成"HTML教程汇总"这样的语义化描述,对目标页的关键词重合度有正向帮助。
爬虫抓取链接时基本不会执行JavaScript跳转。所以如果你的跳转是通过onclick里location.href改写的,搜索引擎的爬虫是读不到的。想让某个链接被爬虫抓取并传递权重,必须保证href属性里是真实的URL,而不是空的或javascript:void(0)。
5. 写在最后:三个我认为值得养成的习惯
聊了这么多,最后分享几个在实际页面里养成的习惯。
**第一个,凡是target="_blank"必带rel="noopener noreferrer"。**就算现代浏览器默认保护了,多写两个词不亏,老浏览器里能防住问题,新浏览器里没有任何副作用。
**第二个,站内链接尽量用真实href,把“点击后执行脚本”的场景分离出来。**能跳页的跳页,不能跳页想清楚当前这个元素应该是链接还是按钮。链接让人预期跳转,按钮让人预期操作,两者混用对用户不友好。
**第三个,多想想移动端。**PC上普通鼠标悬停能看到链接地址,手机上点按没有任何预览,所以锚文本必须自解释。不要写"这里""更多""查看详情",至少要写成"查看订单详情""查看更多文章",这样移动端用户不看地址栏也知道点了会去哪儿。
a标签已经存在了三十多年,语法一直没有变过。但就是这样简单的标签,深入到细节里依然能发展出一套行为规则。把这些规则吃透了,写出来的页面不管在什么样的浏览器环境里都稳定可靠——这件事的价值,并不比一个炫酷的前端框架低。