a标签href与target全解析:从基础用法到安全坑位
1. 一个被写错的属性名让我重新审视了a标签先讲个真事儿。上周帮一个前端群里的朋友排查问题他写的链接死活不跳转代码如下a herfhttps://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 hrefabout.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 hrefmailto:supportexample.com发邮件/a a hreftel:400-888-9999拨打电话/amailto:会打开系统默认邮件客户端还能在地址后带参数预设收件人、主题和内容a hrefmailto:supportexample.com?subject问题反馈body你好我遇到了一个问题联系技术支持/a?后面是查询参数多个参数用连接相当于替用户把邮件标题和正文都填好了。移动端页面里这个用法非常实用用户点了直接把邮件应用唤醒填了内容就能发。tel:则是移动端的神器手机上点了直接进入拨号界面不需要用户手动复制号码。有用户反馈说我点了没反应十有八九是因为在PC上测试桌面浏览器对tel:的支持逻辑是调用本地通讯软件有的系统没装就直接忽略。还有一类是文件下载链接。服务器如果返回了带有Content-Disposition: attachment响应头的文件浏览器就会直接下载不预览。但在前端层面给文件直链加上download属性也能让浏览器把资源当附件下载而不是打开预览a href/files/report.pdf download下载报告/adownload还可以填写另一个文件名告诉浏览器另存为时使用什么名字。不过注意download属性只在同源URL下生效跨域文件它管不了这是浏览器的安全策略。我试过跨域加download属性Chrome直接无视照样打开预览窗口。2.3 锚点回到历史深处的跳转方式锚点用href#id实现点击后页面滚动到对应id的元素位置浏览器地址栏会自动加上#xxx。a href#section-3跳转到第三节/a div idsection-3 h2第三节内容/h2 /div这个用法看起来简单但有个细节值得说。锚点跳转默认是瞬移没有平滑动画。想要滚动过渡效果要么用CSShtml { scroll-behavior: smooth; }要么用JS的scrollIntoView方法。还有一点容易被忽略页面里有多个相同id元素时锚点会跳到文档中第一个匹配的节点。把id当类名一样复用是很多人踩过的坑。锚点还能跨页面跳转到指定位置a href/guide.html#faq去帮助页看FAQ/a这样跳过去的页面会自动定位到idfaq的位置。多页站里的文档中心、FAQ页面很适合用这种方式做目录引导。2.4 javascript:伪协议一个危险又没法完全躲开的写法hrefjavascript:这种写法放在今天会有争议因为从安全角度说直接在HTML属性里塞可执行代码属于内联脚本现代站点通常会被CSP内容安全策略拦截。但老代码里确实大量存在前端的面试里也还偶尔会问。它的本质是浏览器遇到javascript:协议时停止页面跳转把后面的字符串当JavaScript代码执行a hrefjavascript:void(0); onclicksubmitForm()提交/avoid(0)是为了让表达式返回undefined防止页面被替换成返回值。这个写法的意图就是点击时不跳转、只执行脚本。今天再做这个需求更推荐改成按钮或改成拦截默认行为a href# idsubmit-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 targetauthWindow登录/a a href/register targetauthWindow注册/a第一次点登录浏览器会新开一个标签页点完登录再点注册如果authWindow这个名字的窗口还在就不会再新开标签而是直接在那个窗口里加载注册页。如果那个标签页被人手动关掉了再点就又会新建一个。这个特性在某些单点登录流程里可以拿来用。但说实话现在的前端开发里自定义窗口名用得不多知道有这回事儿、遇到时能识别就行。3.3 base标签的全局target覆盖base标签常放在head里它有两个作用一是给页面里所有相对路径设置默认基地址二是设置所有链接的默认target。head base hrefhttps://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的链接上补上rela hrefhttps://外部站点.com target_blank relnoopener noreferrer外部链接/anoopener不让新页面获得window.openernoreferrer不发送来源信息。它会隐式启用noopener效果现在写relnoopener 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必带relnoopener noreferrer。**就算现代浏览器默认保护了多写两个词不亏老浏览器里能防住问题新浏览器里没有任何副作用。**第二个站内链接尽量用真实href把“点击后执行脚本”的场景分离出来。**能跳页的跳页不能跳页想清楚当前这个元素应该是链接还是按钮。链接让人预期跳转按钮让人预期操作两者混用对用户不友好。**第三个多想想移动端。**PC上普通鼠标悬停能看到链接地址手机上点按没有任何预览所以锚文本必须自解释。不要写这里更多查看详情至少要写成查看订单详情查看更多文章这样移动端用户不看地址栏也知道点了会去哪儿。a标签已经存在了三十多年语法一直没有变过。但就是这样简单的标签深入到细节里依然能发展出一套行为规则。把这些规则吃透了写出来的页面不管在什么样的浏览器环境里都稳定可靠——这件事的价值并不比一个炫酷的前端框架低。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →