js检测当前域名是否匹配目标域名,最可靠的方案是以window.location.hostname为基准,通过白名单数组配合正则表达式完成匹配,同时处理子域名、端口和协议边界。
js域名匹配的底层逻辑:从window对象说起
很多前端开发者在做单点登录或支付回调时,都绕不开一个基础问题:当前页面到底在哪个域名下运行,window.location对象提供了几个容易混淆的属性,行业共识认为,hostname才是判断域名匹配的优先选择。
- location.hostname 只返回域名部分,不带端口,例如www.example.com
- location.host 包含端口号,例如www.example.com:8080
- location.origin 还带上协议,例如https://www.example.com
三者区别用一张表看得更清楚:
| 属性 | 返回值示例 | 适用场景 |
|---|---|---|
| location.hostname | www.example.com | 域名匹配校验 |
| location.host | www.example.com:8080 | 端口敏感场景 |
| location.origin | https://www.example.com | 跨域判断 |
首选hostname的理由很直接:去掉端口和协议干扰,只保留纯净的域名,这样你可以把注意力集中在域名本身的匹配逻辑上,业内专家指出,绝大多数域名校验场景都不需要关心端口,因为生产环境通常跑在80或443端口上。
直接比对hostname:最朴素的方案
如果目标域名只有一个,最简单的写法就是全等比较:
const target = 'www.example.com';
const current = window.location.hostname;
if (current === target) {
// 匹配成功
}
这段代码能解决大部分简单场景,但有一个隐患:hostname不保证全小写,虽然大多数浏览器返回小写,但为了保险,建议统一用toLowerCase()处理。
js如何精确匹配主域名而非子域名
这是开发者踩坑最多的地方,假设你的目标域名是example.com,当前页面跑在sub.example.com上,用endsWith()直接判断会出问题:
const hostname = 'sub.example.com';
hostname.endsWith('example.com'); // true
看起来没问题,但如果当前域名是fakeexample.com呢?
'fakeexample.com'.endsWith('example.com'); // true 这里就出错了
这种误判在线上环境可能造成严重的安全隐患,正确的做法是使用边界匹配的正则表达式:
function matchMainDomain(hostname, targetDomain) {
const pattern = new RegExp(`(^|\\.)${targetDomain.replace('.', '\\.')}$`, 'i');
return pattern.test(hostname);
}
matchMainDomain('sub.example.com', 'example.com'); // true
matchMainDomain('fakeexample.com', 'example.com'); // false
关键的细节在于正则里的(^|.)分组,它要求目标域名前面要么是字符串开头,要么有一个点号分隔,这样fakeexample.com就无法蒙混过关了。
允许特定子域名时怎么改
有些场景反而需要排除主域名、只允许指定子域名,比如微信内跳转时,只允许open.weixin.qq.com,不允许weixin.qq.com,这种需求可以用一个精确匹配列表加一个排除列表组合实现,思路是:先判断hostname是否在允许列表中,再判断它是否精确等于被排除的父级域名。
多域名白名单场景:代码怎么写才不臃肿
实际开发中,很少只面对一个目标域名,开发环境是dev.example.com,测试环境是test.example.com,生产环境是www.example.com,可能还要兼容内部域名,手写if-else会越来越长,优雅的做法是维护一个白名单数组。
const allowedDomains = [
'dev.example.com',
'test.example.com',
'www.example.com'
];
function checkDomain(hostname) {
return allowedDomains.includes(hostname);
}
如果白名单中既包含主域名又包含多个子域名,用includes就有点力不从心了,这时候可以把数组换成正则集合:
const domainPatterns = [
/^dev\.example\.com$/,
/^test\.example\.com$/,
/(^|\.)example\.com$/
];
function checkDomain(hostname) {
return domainPatterns.some(pattern => pattern.test(hostname));
}
封装一个通用的域名匹配工具函数
把上面两种方式整合成一个工具函数,既支持精确字符串匹配,也支持正则表达式,还能处理大小写:
function isDomainAllowed(hostname, allowedList) {
const host = hostname.toLowerCase();
return allowedList.some(rule => {
if (rule instanceof RegExp) {
return rule.test(host);
}
return host === rule.toLowerCase();
});
}
用这个函数,调用方只需要维护一个数组,里面既可以放普通字符串,也可以放正则表达式,非常灵活。
正则匹配的坑:端口号、协议与大小写
很多人在匹配域名时会把location.href整个拿来做正则测试,这种做法很容易出错,因为href包含协议、路径、查询参数,任何一处微小变动都会导致匹配失败,正确姿势是只用hostname。
端口号怎么处理
如果在本地开发时需要验证带端口的域名,比如localhost:3000,可以用location.host,但要注意,location.host返回的端口号是字符串,不是数字,匹配时建议把端口单独提取出来,而不是拼在域名正则里。
const { hostname, port } = window.location;
if (hostname === 'localhost' && port === '3000') {
// 本地开发环境
}
大小写与国际化域名
域名本身不区分大小写,但location.hostname在少数旧浏览器中可能原样返回用户输入的内容,统一用toLowerCase()是最稳妥的处理,涉及中文域名时,跨浏览器行为差异更大,最好提前用punycode转换后再比较,不过这属于进阶场景,日常开发中用不上。
Q&A:js域名匹配的常见疑问
Q1:js域名匹配在微信公众号内为什么需要特殊处理?
微信公众号的网页运行在微信内置浏览器中,window.location对象仍然可用,但某些重定向场景(比如OAuth授权跳转)会改变hostname,建议在授权回调页重新执行一次域名匹配,不要依赖页面跳转之前保存的状态。
Q2:检测域名后想读取localStorage,出现跨域报错怎么办?
localStorage按源隔离,域名不同自然无法共享,如果主站和子站需要共享登录状态,通常做法是把token放在主域名下,子域名通过document.domain降级来实现访问,注意document.domain只能设置为当前域的父域,不能随意跳转到一个毫不相干的域名。
Q3:能不能直接用正则匹配location.href完成域名检测?
技术上可以,但不推荐,location.href包含协议、路径、hash等额外信息,正则需要写得更复杂,而且容易漏掉边界情况,把hostname提取出来单独匹配,代码更清晰,测试也更容易覆盖所有场景,所有浏览器对location.hostname的支持都非常一致,这是现代前端开发中少有的零兼容性问题点。
域名匹配没有想象的那么复杂,核心原则只有一个:用location.hostname做基准,用正则处理主域名与子域名的边界,用白名单数组应对多域名场景。把这个逻辑梳理清楚,无论是支付回调还是单点登录,都能稳如泰山。