应遍历所有标签,用link.href获取绝对url,通过new url(href).origin !== location.origin准确识别外部链接,再设target="_blank"并添加rel="noopener noreferrer"。

如何用 JavaScript 找到所有外部链接并设为 target="_blank"
直接遍历 <a></a> 标签,判断是否为外部链接(即 href 以 http 开头且不包含当前域名),再统一设置 target="_blank" 即可。关键不是“加属性”,而是“准确识别外部链接”——很多脚本错误地把相对路径、锚点、mailto: 甚至同站的 HTTPS/HTTP 混合协议都当成外部链接处理。
推荐写法:
document.querySelectorAll('a[href]').forEach(link => {
const href = link.href;
if (!href || href.startsWith('#') || href.startsWith('javascript:') || href.startsWith('mailto:')) return;
try {
const url = new URL(href);
if (url.origin !== location.origin) {
link.target = '_blank';
link.rel = link.rel ? link.rel + ' noopener noreferrer' : 'noopener noreferrer';
}
} catch (e) {
// 忽略非法 URL(如 `//example.com` 在非 http(s) 环境下可能抛错)
}
});
-
link.href用的是只读的绝对 URL(浏览器自动解析),比读link.getAttribute('href')更可靠 - 必须加
rel="noopener noreferrer",否则window.opener可能被恶意利用,且影响新页面性能 -
new URL()能正确处理协议相对 URL(如//cdn.example.com/a.js),但需注意:在 file:// 协议下会报错,所以加了try/catch
为什么不能只靠正则匹配 href 字符串?
用 /^https?:\/\//i 匹配 href 属性值是常见错误。它会把 http://your-site.com/path 当作外部链接(其实不是),也会漏掉 //cdn.example.com 这类协议相对链接,更无法识别 data: 或自定义协议。
真正可靠的判断依据只有一个:link.href 解析后的 origin 是否等于 location.origin。这是浏览器原生行为,兼容性好(IE10+ 支持 URL 构造函数,旧版可用 polyfill)。
- 错误示例:
link.getAttribute('href').startsWith('http')→ 把同站绝对路径也当外部链接 - 错误示例:
href.indexOf(location.hostname) === -1→ 忽略子域差异(blog.example.com和example.com通常算不同源) - 正确依据永远是
url.origin !== location.origin,不是字符串包含关系
什么时候该跳过某些链接?
不是所有带 http 的链接都要开新窗口。比如站内文档 PDF、API 接口地址、下载链接(download 属性存在时),用户预期就是当前页跳转或触发下载。硬塞 target="_blank" 会破坏体验。
- 显式声明
download属性的链接(<a href="a.pdf" download></a>)应保留原行为 - 有
data-external="false"或class="internal"等人工标记的,优先服从标记 - 含
javascript:void(0)、#、tel:的链接必须跳过,否则会打开空白页 - 如果页面本身运行在非标准端口(如
http://localhost:8080),location.origin包含端口,对比才准确
放在哪里执行?DOMContentLoaded 还是 load?
必须等 DOM 加载完成,但不必等全部资源(图片、CSS)加载完。所以挂到 DOMContentLoaded 最合适。如果脚本放在 里,要加 defer;如果放底部,直接执行即可。
避免写成 window.onload —— 它等所有资源加载完毕,用户可能已经点过链接了,导致部分链接没被处理。
- 推荐写法:
document.addEventListener('DOMContentLoaded', () => { /* 上面的遍历逻辑 */ }); - 如果页面用框架(如 Vue/React),应在组件挂载后执行,而非全局脚本 —— 否则动态插入的链接不会被处理
- 服务端渲染(SSR)页面要注意:客户端 JS 执行前,链接已渲染完成,所以这个脚本仍有效;但若用
innerHTML动态插入新链接,需单独调用一次处理函数
最常被忽略的一点:没有检查 link.target 是否已被手动设为其他值(如 _self)。如果业务代码已有明确控制,强制覆盖可能引发冲突。稳妥做法是只对未设置 target 的链接生效,或加白名单机制。











