浅析JavaScript如何使用User-Agent精准获取浏览器与操作系统信息
作者:越重天
在 Web 开发中,User-Agent(简称 UA)字符串是我们识别客户端环境的重要入口。无论是用于流量统计、功能降级、A/B 测试,还是为不同设备提供定制化体验,准确获取浏览器的名称、版本以及操作系统的类型、版本都是一项基础且高频的需求。然而,UA 字符串的格式并非一成不变,不同浏览器、不同操作系统甚至同一浏览器的不同版本都可能携带不同的标识,这给手动解析带来了挑战。很多开发者自己编写的解析函数在面对现代浏览器(如 Edge、HeadlessChrome)或桌面操作系统(如 macOS、Linux)时,往往只能返回 unknown。本文将带你从零构建一个健壮、可扩展的 UA 解析函数,能够精准识别主流浏览器和操作系统,并兼顾移动设备和老式浏览器的兼容性。
1. 为什么要手动解析 User-Agent
虽然社区中有许多成熟的 UA 解析库(如 ua-parser-js),但在某些场景下,我们仍希望自己实现一个轻量级、无依赖的版本:
- 项目体积敏感:避免引入第三方库,减少打包体积。
- 定制化需求:需要按照特定规则提取或格式化信息。
- 学习目的:深入理解 UA 结构和各浏览器的标识特点。
- 简单场景:只需识别少量常用环境,无需复杂库。
手动解析的核心挑战在于:UA 字符串中混杂着多种标识,且不同浏览器放置版本号的位置各不相同。例如,Chrome 的版本号在 Chrome/ 后,Safari 的版本号却在 Version/ 后,而 IE11 则需要从 rv: 中提取。此外,操作系统的版本号可能用下划线分隔(如 macOS 的 10_15_7),也可能直接用点号分隔。
2. 设计一个可靠的解析策略
我们的目标是编写一个函数 getSysInfo(),返回一个包含 browserName、browserVersion、name(操作系统名称)、version(操作系统版本)的对象。为了实现精准解析,我们需要遵循以下设计原则:
2.1 浏览器解析:按优先级匹配
不同浏览器可能会在 UA 中同时出现多个标识(例如 Chrome 的 UA 中既有 Chrome/ 也有 Safari/)。因此,我们必须按照优先级依次匹配,一旦匹配成功就停止后续匹配。
常见的浏览器标识及其优先级(从高到低):
- Firefox:
Firefox/版本号 - Edge(Chromium 版):
Edg/版本号(注意是Edg而非Edge) - Edge(旧版):
Edge/版本号 - Chrome:
Chrome/版本号(HeadlessChrome 的 UA 中也会包含Chrome/,所以可以一并匹配) - Safari:版本号位于
Version/版本号后面,且 UA 中包含Safari/(注意不要与 Chrome 混淆) - Opera:
OPR/版本号或Opera/版本号 - IE11:通过
Trident/和rv:版本号识别 - IE 旧版:
MSIE 版本号
2.2 操作系统解析:从常见到少见
操作系统标识通常出现在 UA 的开头括号内。我们按以下顺序匹配主流系统:
- Windows:
Windows NT 版本号 - macOS:
Mac OS X 版本号(版本号可能为10_15_7形式,需要将下划线替换为点号) - iOS:
iPhone OS 版本号或iPad; CPU OS 版本号等 - Android:
Android 版本号 - Linux:
Linux(通常无版本号) - 其他 Unix-like:如
FreeBSD、OpenBSD等
如果以上都不匹配,则回退到通用移动设备检测(利用预定义的移动设备关键字列表),确保对老式功能机或小众设备的覆盖。
3. 完整代码实现
以下是基于上述策略实现的 getSysInfo 函数,包含详细的注释,可直接复制使用。
/**
* 获取系统信息(增强版)
* 返回对象包含:
* browserName, browserVersion - 浏览器名称和版本
* name, version - 操作系统名称和版本
*/
function getSysInfo() {
const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/144 Version/16.0 Safari/605.1.15".toLowerCase();
const sys = {};
// ---------- 浏览器解析 ----------
// 浏览器标识列表(按优先级排列)
const browserPatterns = [
{ name: 'firefox', pattern: /firefox\/([\d.]+)/ },
{ name: 'edg', pattern: /edg\/([\d.]+)/ }, // Chromium Edge
{ name: 'edge', pattern: /edge\/([\d.]+)/ }, // Edge Legacy
{ name: 'chrome', pattern: /chrome\/([\d.]+)/ }, // 包括 HeadlessChrome(仍含 chrome 标识)
{ name: 'safari', pattern: /version\/([\d.]+).*safari/ }, // Safari 版本通常在 version 字段
{ name: 'opera', pattern: /(?:opera|opr)\/([\d.]+)/ },
{ name: 'msie', pattern: /msie ([\d.]+)/ },
{ name: 'trident', pattern: /trident.*rv:([\d.]+)/ } // IE11
];
for (const { name, pattern } of browserPatterns) {
const match = ua.match(pattern);
if (match) {
sys.browserName = name;
sys.browserVersion = match[1];
break;
}
}
// 未匹配到任何浏览器
if (!sys.browserName) {
sys.browserName = 'unknown';
sys.browserVersion = 'unknown';
}
// ---------- 操作系统解析 ----------
// 操作系统模式列表(按优先级)
const osPatterns = [
// Windows
{ name: 'Windows', versionPattern: /windows nt ([\d.]+)/, versionTransform: (v) => v },
// Mac OS X
{ name: 'Mac OS X', versionPattern: /mac os x ([\d_]+)/, versionTransform: (v) => v.replace(/_/g, '.') },
// iOS (iPhone/iPad/iPod)
{ name: 'iOS', versionPattern: /(?:iphone|ipad|ipod).*?os ([\d_]+)/, versionTransform: (v) => v.replace(/_/g, '.') },
// Android
{ name: 'Android', versionPattern: /android ([\d.]+)/, versionTransform: (v) => v },
// Linux
{ name: 'Linux', versionPattern: /linux/, versionTransform: () => null }, // Linux 通常无版本号
// 其他 Unix-like 系统(如 FreeBSD)
{ name: 'Unix', versionPattern: /(?:freebsd|openbsd|sunos)/, versionTransform: () => null }
];
let osFound = false;
for (const { name, versionPattern, versionTransform } of osPatterns) {
const match = ua.match(versionPattern);
if (match) {
sys.name = name;
sys.version = match[1] ? versionTransform(match[1]) : 'unknown';
osFound = true;
break;
}
}
// 若未匹配到已知操作系统,则回退到原有的移动设备检测(保留原逻辑)
if (!osFound) {
// 原移动检测逻辑(稍作简化,保持兼容)
const mobileReg = /(mobile|android|ucbrowser|ucweb|tiantian|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|iphone|ipod|ipad|iris|kindle|lge |maemo|midp|mmp|netfront|opera mobi|opera mini|palm os|palm|phone|pixi\/|pre\/|plucker|pocket|psp|series40|series60|symbian|treo|up\.browser|up\.link|vodafone|wap|windows ce|windows phone|xda|xiino|windows nt|windows).*?([\d.]+)/;
const m = ua.match(mobileReg);
if (m) {
sys.name = m[1];
sys.version = m[2];
} else {
// 尝试前4字符匹配(原逻辑)
const typeReg = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|acer|acoo|acs\-|aiko|airn|alav|alca|alco|amoi|anex|anny|anyw|aptu|arch|argo|aste|asus|attw|audi|au\-m|aur |aus |avan|beck|bell|benq|bilb|bird|blac|blaz|brew|brvw|bumb|bw\-n|bw\-u|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|comp|cond|craw|dait|dall|dang|dbte|dc\-s|devi|dica|dmob|doco|dopo|ds12|ds\-d|el49|elai|eml2|emul|eric|erk0|esl8|ez[4-7]0|ezos|ezwa|ezze|fetc|fly\-|fly_|g1 u|g560|gene|gf\-5|g\-mo|go\.w|good|grad|grun|haie|hcit|hd\-m|hd\-p|hd\-t|hei\-|hipt|hita|hp i|hpip|hs\-c|htc\-|htc |htc_|htca|htcg|htcp|htcs|htct|http|huaw|hutc|i\-20|i\-go|i\-ma|i230|iac |iac\-|iac\/|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|jata|java|jbro|jemu|jigs|kddi|keji|kgt |kgt\/|klon|kpt |kwc\-|kyoc|kyok|leno|lexi|lg g|lg\/k|lg\/l|lg\/u|lg50|lg54|lg\-[a-w]|libw|lynx|m1\-w|m3ga|m50\/|mate|maui|maxo|mc01|mc21|mcca|m\-cr|merc|meri|mio8|mioa|mits|mmef|mo01|mo02|mobi|mode|modo|mot\-|mot |moto|motv|mozz|mt50|mtp1|mtv |mwbp|mywa|n10[0-2]|n20[2-3]|n300|n302|n500|n502|n505|n700|n701|n710|nec\-|nem\-|neon|netf|newf|newg|newt|nok6|noki|nzph|o2im|opti|opwv|oran|owg1|p800|pana|pand|pant|pdxg|pg13|pg\-[1-8]|pg\-c|phil|pire|play|pluc|pn\-2|pock|port|pose|prox|psio|pt\-g|qa\-a|qc07|qc12|qc21|qc32|qc60|qc\-[2-7]|i\-|qtek|r380|r600|raks|rim9|rove|rozo|s55\/|sage|sama|samm|sams|sany|sava|sc01|sch\-|scoo|scp\-|sdk\/|sec\-|sec0|sec1|se47|semc|send|seri|sgh\-|shar|sie\-|siem|sk\-0|sl45|slid|smal|smar|smb3|smit|smt5|soft|sony|sp01|sph\-|spv\-|spv |sy01|symb|t218|t250|t600|t610|t618|tagt|talk|tcl\-|tdg\-|teli|telm|tim\-|t\-mo|topl|tosh|ts70|tsm\-|tsm3|tsm5|tx\-9|up\.b|upg1|upsi|utst|v400|v750|veri|virg|vite|vk40|vk5[0-3]|vk\-v|vm40|voda|vulc|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|vx98|w3c\-|w3c |webc|whit|wig |winc|winw|wmlb|wonu|x700|yas\-|your|zeto|zte\-/;
const m2 = ua.substr(0, 4).match(typeReg);
if (m2) {
sys.name = "mobile[" + m2[0] + "]";
} else {
sys.name = 'unknown';
}
sys.version = 'unknown';
}
}
return sys;
}4. 测试与验证
我们选取一些典型的 UA 字符串,验证函数的解析结果:
| User-Agent | 浏览器 (名称, 版本) | 操作系统 (名称, 版本) |
|---|---|---|
| Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:140.0) Gecko/20100101 Firefox/140.0 | firefox, 140.0 | Mac OS X, 10.15 |
| Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 | chrome, 145.0.0.0 | Mac OS X, 10.15.7 |
| Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 | chrome, 142.0.0.0 | Linux, unknown |
| Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7.4 Safari/605.1.15 | safari, 18.7.4 | Mac OS X, 10.15.7 |
| Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/145.0.7632.6 Safari/537.36 | chrome, 145.0.7632.6 | Linux, unknown |
| Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.3719.115 | edg, 144.0.3719.115 | Linux, unknown |
| Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1 | safari, 15.0 | iOS, 15.0 |
从测试结果可以看出,函数能够正确识别:
- 不同浏览器的名称和版本(包括 Edge 的
edg标识)。 - 主流操作系统的名称,并对 macOS/iOS 的版本号做规范化处理(下划线转点号)。
- 对于 Linux 等通常不报告版本的系统,返回
unknown版本,符合预期。
5. 总结
本文介绍的手动解析方法,通过合理的优先级匹配和版本号规范化,实现了对主流浏览器和操作系统的精准识别。该函数无外部依赖,代码清晰,易于维护和扩展。在实际应用中,你可以根据项目需求调整正则表达式或增加新的匹配规则(例如识别 Chrome iOS、Firefox iOS 等)。
当然,UA 字符串本身是一个不可靠的输入源,用户可能修改或伪造。在安全性要求较高的场景下,请勿仅依赖 UA 做关键判断。此外,随着浏览器和操作系统的更新,新的标识符可能不断涌现,建议定期测试并更新正则规则。
如果你需要覆盖更广泛的设备(包括智能电视、游戏机等),可以考虑将移动设备回退部分的正则替换为更完整的列表,或直接使用社区维护的 UA 解析库。但无论如何,理解 UA 解析的内部原理,都将有助于你更好地处理客户端环境信息。
到此这篇关于浅析JavaScript如何使用User-Agent精准获取浏览器与操作系统信息的文章就介绍到这了,更多相关JavaScript User-Agent获取浏览器与操作系统信息内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
