前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南

0. 总论:为什么"识别谁"不如"识别能做什么"

  • 核心原则优先能力检测(feature detection),谨慎 UA/指纹
  • 时代背景 :浏览器正在缩减传统 UA (User-Agent)暴露的信息;Chromium 系系(Chrome/Edge/Opera 等)已完成 UA 精简,推荐改用 User-Agent Client Hints(UA-CH) 。这意味着靠 UA 解析品牌/系统型号的老办法会越来越不准,或直接拿不到信息。chromium.orgChrome for Developers

1. 能力检测(Feature Detection)

1.1 基本写法

javascript 复制代码
// 例:Geolocation
if ('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition(({ coords }) => {
    console.log(coords.latitude, coords.longitude);
  }, console.error, { enableHighAccuracy: true });
} else {
  // 回退或禁用依赖功能
}

1.2 模式:渐进增强 & 有条件加载

dart 复制代码
// 例:仅在支持时加载高开销特性/包
(async () => {
  if ('serviceWorker' in navigator) {
    await navigator.serviceWorker.register('/sw.js');
  }
  if ('webkitSpeechRecognition' in window) {
    const { default: initVoice } = await import('./voice.js');
    initVoice();
  }
})();

1.3 安全的 typeof/ in 检查

javascript 复制代码
if (typeof document.querySelector === 'function') { /* ... */ }
if ('IntersectionObserver' in window) { /* ... */ }

2. "安全能力检测"(以 typeof 验证为例)

  • 目的:避免直接调用未定义对象/方法导致运行时错误。
  • 要点:先判空/类型,再用。
  • 扩展:对实验性 API ,加 try/catch + 超时/降级,保证在功能缺失时快速恢复。
javascript 复制代码
function safeGetBattery() {
  if (typeof navigator.getBattery === 'function') {
    return navigator.getBattery();
  }
  return Promise.reject(new Error('Battery API unsupported'));
}

注:Battery Status API 在多数现代桌面浏览器中并不广泛可用/已被限制 ,仅在部分环境可用,且常要求 HTTPSMDN Web Docs+1


3. 基于能力检测进行浏览器分析

3.1 "检测特性"推断内核(只作为弱信号

ini 复制代码
const style = document.documentElement.style;
const hints = {
  webkit: 'WebkitAppearance' in style,
  moz:    'MozAppearance' in style,
};
if (hints.webkit) {/* 可能 WebKit/Blink */}

警告:多内核趋同 ,厂商会暴露"兼容属性",这类推断容易误判

3.2 "探嗅"与反模式

  • 典型 IE 检测(历史场景):

    ini 复制代码
    const isIE = !!document.documentMode; // 仅做老项目兜底
  • 现代建议 :除非是已知 Bug 的特定补丁,否则不要写"品牌 == X 才启用功能"的判断。

3.3 能力检测的局限

  • 无法精确版本定位(有时需要版本来规避某个已知缺陷)。
  • 新旧实现可能部分存在/行为差异
  • 隐私策略 相冲突(比如某些信息被钳制/随机化)。
    结论:当真的需要品牌/版本 时,用 UA-CH服务器端协商 获取,且做好不可用的兜底。MDN Web Docs

4. 用户代理检测(UA 解析)

arduino 复制代码
console.log(navigator.userAgent); // 传统 UA 字符串
  • 今日状态:Chromium 已对 UA 做信息缩减 (如 Android 设备型号统一、版本模糊等),不再可靠 。迁移路径为 UA-CHchromium.orgAkamai

5. 用户代理历史(速写)

  • 早期浏览器:UA 简单直白。
  • Netscape 3 / IE3 :网站按 UA 分流,IE 为拿到"增强版"开始冒充 Mozilla (UA 以 Mozilla/ 开头的传统至今犹存)。Stack Overflow
  • Netscape 4 / IE4--8:UA 里相互模仿、兼容标签杂糅。
  • Gecko(Firefox) :UA 含 Gecko/Firefox/
  • WebKit(Safari) / Konqueror(KHTML)AppleWebKit / KHTML 标记。
  • Chrome(后改 Blink) :UA 同样带上 AppleWebKit/Safari 兼容串。
  • Opera :曾可"伪装/掩盖"自己为 IE/Firefox。人类编码
  • iOS / Android :移动 UA 包含设备/平台线索,但如今在 Chromium 上已被缩减chromium.org

小结:UA 历史就是"一路互相假扮"的历史------这也是今天倡导能力检测 + UA-CH 的根本原因。Niels Leenheer


6. 浏览器分析

6.1 伪造 UA 的普遍性

  • 开发者工具可覆盖/修改 UAUA-CH ,爬虫/中间层也可改写请求头。Chrome for Developers
  • 结论:任何仅靠 UA 的结论都必须打折

6.2 实战分析策略(建议的三步)

  1. 能力矩阵:先拉一组你关心的关键能力。
javascript 复制代码
const caps = {
  mjs: !!('noModule' in document.createElement('script')),
  sw: 'serviceWorker' in navigator,
  webgl2: !!document.createElement('canvas').getContext('webgl2'),
  webrtc: !!(window.RTCPeerConnection || window.webkitRTCPeerConnection),
  h2: typeof navigator.connection?.effectiveType === 'string', // 仅作弱信号
};
  1. UA-CH(若可用)补充品牌/平台版本(详见 §8.2)。
  2. 已知缺陷白名单 :将"针对 Bug 的补丁"与品牌/版本绑定(例如只在 brand==X && version<Y 才走分支),并设过期逻辑。

7. 软件与硬件检测(Navigator/Screen)

这些信息越来越模糊或受限,要按"性能自适应"思路使用,而非指纹。

  • navigator.oscpu已废弃/不推荐 (主要在 Firefox 曾可用)。别依赖。MDN Web Docs

  • navigator.vendor / navigator.platform :可被伪装,不稳定;iOS 上所有浏览器都用 WebKit,平台信息迷惑性强

  • screen.colorDepth / screen.pixelDepth :多为 24;仅适合做位图/色彩降级策略。

  • screen.orientation:实用但要监听变化。

    ini 复制代码
    const o = screen.orientation;
    console.log(o?.type, o?.angle);
    o?.addEventListener?.('change', () => {/* 适配布局 */});
  • navigator.hardwareConcurrency :逻辑核数,但在部分浏览器会被钳制 (例如上限 4/8)以降低指纹化风险。不要做"=1 就禁用某功能"这类硬判断。MDN Web DocsCan I Use

  • navigator.deviceMemory :近似内存(GB),支持有限 、仅 HTTPS。用于资源等级 决策(轻/重资源)。MDN Web Docs+1

  • navigator.maxTouchPoints:可用于判断是否具备触控能力(仍有误报可能)。


8. 浏览器元数据

8.1 Geolocation API

  • 用户授权 ;可设置精度/超时。适合可选体验而非强依赖。

8.2 Connection State / Network Information API

  • navigator.connection 暴露 effectiveTypedownlinkrtt 等。主要是 Chromium 支持 ;跨浏览器不可依赖,建议仅用作"质量自适应"信号。MDN Web Docs+2MDN Web Docs+2
javascript 复制代码
const conn = navigator.connection;
if (conn) {
  console.log(conn.effectiveType); // slow-2g|2g|3g|4g
  conn.addEventListener('change', () => {/* 切换码率/图片质量 */});
}

8.3 Battery Status API

  • 可用性低 、常被限制;如要用,请先能力检测 并提供降级。MDN Web Docs+1

8.4 Global Privacy Control(GPC)

  • navigator.globalPrivacyControl 反映用户的 Sec-GPC 隐私意愿;合规相关场景可参考。MDN Web Docs

9. 硬件(性能相关信号)

  • 处理器核心数navigator.hardwareConcurrency(注意被钳制)。MDN Web DocsCan I Use
  • 设备内存大小navigator.deviceMemory(近似、支持有限)。MDN Web Docs
  • 最大触点数navigator.maxTouchPoints(仅作 UI 适配提示,别用来断言"设备类型")。

10. 现代替代:User-Agent Client Hints(UA-CH)

目标:用"按需、可控"的提示替代冗长 UA 字符串 ;客户端(JS)与服务端(HTTP)均可用。注意Safari 与 iOS Chrome 不支持 ,必须兜底MDN Web DocsStack Overflow

10.1 客户端(JS)

ini 复制代码
if (navigator.userAgentData?.getHighEntropyValues) {
  const ua = await navigator.userAgentData.getHighEntropyValues([
    'architecture', 'model', 'platform', 'platformVersion', 'uaFullVersion'
  ]);
  console.log(ua);
}
// 低熵(不需要权限/回合)的字段:brands, mobile, platform 等

该 API 实验性/有限可用 ,使用前务必能力检测与降级处理。MDN Web Docs+1

10.2 服务端(HTTP)

  • 响应头声明你愿意接收哪些 Hint(下次请求生效),并配合 Vary
makefile 复制代码
Accept-CH: Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Model, Sec-CH-UA-Full-Version-List
Vary: Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Model, Sec-CH-UA-Full-Version-List

11. 典型"浏览器分析"配方(可直接抄)

11.1 环境快照(调试面板/日志)

javascript 复制代码
export async function snapshot() {
  const out = {
    ua: navigator.userAgent,
    brands: navigator.userAgentData?.brands,
    platform: navigator.userAgentData?.platform ?? navigator.platform,
    mobile: navigator.userAgentData?.mobile ?? /Mobi|Android/i.test(navigator.userAgent),
    lang: navigator.language,
    hw: navigator.hardwareConcurrency,
    mem: navigator.deviceMemory,
    touch: navigator.maxTouchPoints,
    net: navigator.connection?.effectiveType
  };
  try {
    if (navigator.userAgentData?.getHighEntropyValues) {
      Object.assign(out, await navigator.userAgentData.getHighEntropyValues([
        'architecture','model','platformVersion','uaFullVersion'
      ]));
    }
  } catch {}
  return out;
}

11.2 "性能自适应"开关(建议)

ini 复制代码
const tier = (() => {
  const mem = navigator.deviceMemory || 4;     // 假设 4GB
  const cpu = Math.min(navigator.hardwareConcurrency || 4, 8);
  const slowNet = navigator.connection?.effectiveType?.includes('2g') 
               || navigator.connection?.saveData;
  if (mem <= 2 || cpu <= 2 || slowNet) return 'lite';
  return 'full';
})();

if (tier === 'lite') {
  // 小图、低码率、懒加载更多、禁用昂贵动画
}

11.3 iOS/Safari 等"品牌级"判断(仅为兼容补丁)

javascript 复制代码
const ua = navigator.userAgent;
// iOS Safari(弱判断):包含 iPhone|iPad,含 Safari 且不含 CriOS/FxiOS
const isIOSSafari = /iP(hone|od|ad)/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS/.test(ua);
// 注意:iOS 所有浏览器引擎均为 WebKit,此判断只用于个别已知 Bug 规避。

12. 风险与合规

  • 隐私与反指纹 :浏览器可能对 hardwareConcurrency 等值钳制/随机化 ;Battery、NetworkInfo 等 API 受限或缩水,不要做用户身份推断Can I Use
  • 可用性差异 :Network Information、UA-CH 等在 Safari/iOS 不可用或行为不同 ;必须能力检测 + 回退MDN Web DocsStack Overflow
  • 安全上下文 :UA-CH、Device Memory、Battery 等常要求 HTTPSMDN Web Docs+1

13. 取舍清单(TL;DR)

  • 首选:能力检测 + 渐进增强 + 性能自适应(mem/cpu/net 仅作"质量档位"而非身份)。
  • 需要品牌/版本时 :先 UA-CH(可用则用),否则有限度 UA + 能力结合。
  • ⚠️ 避免:仅凭 UA/指纹做关键功能开关;把硬件值当"真相"。
  • 🧪 实验性 :UA-CH JS 接口(userAgentData)、Network Information、Battery------都要判可用 并提供降级。MDN Web Docs+2MDN Web Docs+2

14. 附:为什么今天还会看到 Mozilla/5.0

因为 90 年代网站用 UA "给 Netscape 发高级版页面",其他浏览器只好自称 Mozilla 来获得同等待遇,于是这串历史遗留一直保留到今天。Stack OverflowNiels Leenheer

相关推荐
Juchecar几秒前
常见的 HTML 标签及 CSS 选择器速查表
前端
前端程序猿i13 分钟前
用本地代理 + ZIP 打包 + Excel 命名,优雅批量下载跨域 PDF
前端·javascript·vue.js·html
Danny_FD22 分钟前
Vue2 中使用vue-markdown实现编辑器
前端·javascript·vue.js
用户游民22 分钟前
Flutter 项目热更新加载 libapp.so 文件
前端
coding随想22 分钟前
Vue和React对DOM事件流的处理方法解析
前端
用户479492835691523 分钟前
字节面试官:forEach 为什么不能被中断?
前端·javascript
ccnocare24 分钟前
window.electronAPI.send、on 和 once
前端·electron
tager29 分钟前
🍪 让你从此告别“Cookie去哪儿了?”
前端·javascript·后端
阿吉被迫了解低代码34 分钟前
前端:“学算法?狗都不... !”
前端
前端赵哈哈40 分钟前
Vue 3 + TypeScript 项目模板
前端·vue.js·vite