先给结论
判断JS中当前域名是否匹配目标域名,推荐优先用 window.location.hostname(或 location.host)与目标值做规范化比较,并显式处理端口、子域和协议,避免用 includes 做模糊匹配。 这看起来简单,但实际项目里因为“域名”的定义差异,常常出现误判比如用户从 https://www.example.com 访问,而你的目标是 example.com,直接相等必然失败。
为什么精准匹配域名比想象中复杂
很多人写第一版代码时,都写过类似 location.hostname.includes("example.com") 这种表达式,它确实能应对子域变化,但也埋了两个雷:notexample.com 会误中,example.com.evil.com 也会误中,更隐蔽的问题是,location.hostname 在浏览器里默认转成小写,但如果你在代码里比较的字符串来自配置文件或后端接口,大小写不一致就会漏判。
“域名”到底指哪一层
- 裸域:
example.com - 带
www:www.example.com - 子域:
blog.example.com - 带端口:
example.com:8080 - 带路径:
example.com/path/page
在实际业务里,你通常关心的是“当前站点是不是属于某个主域”,比如电商系统里判断是否处于微信支付授权域名下,或者前端灰度发布时判断当前环境是否为测试域名,这里的关键是:你要匹配的是“完整主机名”还是“主域”,决策不同,代码逻辑差别很大。
常见的错误写法及其后果
- 使用
location.href.indexOf("target.com") > -1:只要URL字符串里出现过一次,就判定通过,攻击者构造https://target.com.attacker.io也能绕过,这在支付回调场景下属于严重漏洞。 - 忽略端口差异:
location.host包含端口,而location.hostname不包含,如果你用host去比较一个不带端口的配置项,开发环境(本地常带8080)就会莫名其妙失败。 - 忽略末尾的点:少数特殊场景下,FQDN(完全限定域名)以点结尾,
example.com.,某些DNS环境会返回这种格式,不处理会导致比较结果为false。
推荐方案:按需选比较基准
首先明确你要比较的“当前域名”来源:
location.hostname:仅主机名,不含协议和端口,最常用。location.host:主机名加端口,如example.com:8443。location.origin:协议加主机名加端口,如https://example.com:8443,不包含路径。
精确匹配裸域和顶级子域
行业共识认为,匹配主域时应当先提取“注册域”,而非简单字符串包含,但浏览器端没有内置的公共后缀列表(PSL)解析器,所以轻量做法是:
function isSameMainDomain(hostname, target) {
const cleanHost = hostname.toLowerCase().replace(/\.$/, '');
const cleanTarget = target.toLowerCase().replace(/\.$/, '');
return cleanHost === cleanTarget || cleanHost.endsWith('.' + cleanTarget);
}
这个函数会把 www.example.com 和 example.com 都判定为属于 example.com 主域,注意 endsWith('.' + cleanTarget) 防止了 badexample.com 的误判。
处理端口和协议
如果你需要严格匹配“当前站点是否完全等于某个带端口的地址”,建议用 location.host 而不是拼字符串:
const ALLOWED_HOST = 'api.example.com:8443';
if (location.host === ALLOWED_HOST) {
// 执行白名单逻辑
}
如果目标配置里有协议,那就比较 location.origin,但要注意,location.origin 在IE中不受支持,如果还要兼容老浏览器,需要手动拼接:
const origin = window.location.protocol + '//' + window.location.host;
正则匹配的进阶用法
某些场景下你需要匹配一组域名,.example.com 这种通配形式,你可以把通配符转成正则:
function matchWildcardDomain(currentHost, pattern) {
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\/g, '.') + '$', 'i');
return regex.test(currentHost);
}
这个函数能匹配 .example.com 对 blog.example.com 和 www.example.com 都能返回 true,但对 example.com 本身返回 false,因为通配符一般要求至少一级子域。
如何解决跨域场景下的域名获取问题
在iframe或跨域嵌套页面中,window.location.hostname 能拿到当前窗口的域名,但如果你想要拿到父页面的域名,直接访问会抛出跨域错误,这种情况的常见解法是让父页面在URL参数里显式传递来源域名,或者通过 postMessage 通信。
利用 `document.referrer` 的局限性
document.referrer 在跨域跳转中常常为空,特别是从HTTPS页面跳到HTTP页面,或者用户直接输入地址时,所以不要依赖它作为域名判断的唯一依据。
后端配合才是硬道理
前端域名校验只能防误操作,防不了恶意伪造,因为所有前端变量都可被篡改,真正的权限判断必须在后端完成,前端拿到校验结果后,只能用来做展示层逻辑,比如根据域名切换接口地址、设置不同站的统计代码。
不同业务场景下的匹配策略和价格考量
在实际建站时,不同项目对域名匹配的需求差异很大,比如做站群的朋友常常问:js 如何精准检测当前域名是否匹配目标域名,并且要批量判断当前访问的是哪个站,从而加载对应资源,这类需求一般走配置文件驱动,把所有域名映射表放在一个JS对象里,用上面的 hostname 做key。
按环境区分匹配规则
- 本地开发:
localhost和0.0.1要单独处理,location.hostname返回localhost,而你不会在正式配置里写这个。 - 测试环境:通常用带
-test或-staging的二级域名,建议用endsWith匹配主域后缀,而不是逐个枚举。 - 生产环境:精确匹配为主,子域或裸域要明确列出。
很多SaaS服务在做域名绑定白名单时,也会要求用户填写“授权域名”,如果你恰好在开发类似的配置界面,可以加上一个“检测当前域名”按钮,通过这篇文中的方法把结果展示给用户,方便他复制粘贴,对于预算敏感的中小企业,直接使用现成的云解析和对象存储服务,也可以减少自研成本,但前端域名判断的代码依然省不掉。
测试用例你应该覆盖哪些边界
单测里至少要包含这些输入,否则上线后容易出意外:
- 空字符串
- 带协议:
https://example.com - 带路径:
example.com/path - 带端口:
example.com:8080 - 大写:
EXAMPLE.COM - 末尾加点:
example.com. - 相似恶意域:
example.com.evil.com - 国际域名:
xn--fiqs8s(中文域名转punycode)
推荐的测试策略
用Node的 assert 或者简单的手写断言都行,关键是把“期望结果”写清楚,比如你定义了“主域匹配”规则,www.example.com 相对 example.com 应该返回 true;如果你定义“完全匹配”,应该返回 false,这两种语义不能在一个函数里混着用。
常见问题:为什么我的匹配总是差一步
大小写问题
虽然浏览器返回的 hostname 基本是小写,但配置文件可能写成 Example.COM,统一在比较前调 toLowerCase(),成本最低也最保险。
IPv4和IPv6的坑
访问 http://127.0.0.1:8080 时 location.hostname 0.0.1,没问题,但IPv6像 http://[::1]:8080 时,location.hostname 返回 [::1] 还是 :1 在不同浏览器有历史差异,如果你的测试环境用到IPv6,建议取 location.host 然后去掉尾部的端口部分,或者直接比较 location.origin。
域名与国际化(IDN)
中文域名在浏览器地址栏显示为中文,但 location.hostname 返回的是punycode编码,xn--fiqs8s,如果你在配置里写中文,必须手动转码再比较。
Q&A:关于js域名检测的高频疑问
问:js如何精准检测当前域名是否匹配目标域名,能不能只用一个正则解决?
可以,但不建议,正则适合匹配固定模式,^(.+\.)?example\.com$,但一旦目标域名从外部配置传入,你需要动态转义正则里的点号,否则 会被当作任意字符匹配,用上面提到的“先移除尾部点,再比较相等或属于子域”的函数,可读性和可维护性都更好。
问:在浏览器插件里获取当前域名的方法一样吗?
不一样,Chrome扩展的 content_script 里可以用 location.hostname,因为脚本运行在页面上下文,但 background 脚本里你需要通过 tabs.query 拿到 tab.url,再用 new URL(tab.url).hostname 来提取,而且扩展权限不同,某些API拿到的URL可能带额外的 chrome-extension:// 开头,这时要先做协议过滤。
问:判断域名匹配时,`location.hostname` 和 `location.host` 哪个更实用?
日常开发中,location.hostname 更实用,因为它屏蔽了端口差异,适合判断“当前站是哪个站”。location.host 只在需要区分同一域名下的不同端口服务时才会用到,比如本地前端连 8080 的API和 9090 的管理后台,按2026年主流浏览器行为来看,这两个属性的返回值都非常稳定,不会因为协议变化而改变,只要记住 location.host 会带上端口号即可。