Skip to main content

[JS] 客户端检测

摘记<Javascript高级程序设计> -- 马特 2020版 第十三章

客户端检测

虽然浏览器厂商齐心协力想要实现一致的接口,但事实上仍然是每家浏览器都有自己的长处与不足。跨平台的浏览器尽管版本相同,但总会存在不同的问题。这些差异迫使 Web 开发者要么面向最大公约数而设计,要么(更常见地)使用各种方法来检测客户端,以克服或避免这些缺陷。

能力检测

能力检测(又称特性检测)即在 JavaScript 运行时中使用一套简单的检测逻辑,测试浏览器是否支持某种特性。这种方式不要求事先知道特定浏览器的信息,只需检测自己关心的能力是否存在即可。能力检测的基本模式如下:

if (object.propertyInQuestion) {
// 使用 object.propertyInQuestion
}

比如,IE5 之前的版本中没有 document.getElementById()这个 DOM 方法,但可以通过document.all 属性实现同样的功能。为此,可以进行如下能力检测:

function getElement(id) {
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
return document.all[id];
} else {
throw new Error("No way to retrieve element!");
}
}

用户代理检测

用户代理检测通过浏览器的用户代理字符串确定使用的是什么浏览器。用户代理字符串包含在每个HTTP 请求的头部,在 JavaScript 中可以通过 navigator.userAgent 访问。在服务器端,常见的做法是根据接收到的用户代理字符串确定浏览器并执行相应操作。而在客户端,用户代理检测被认为是不可靠的,只应该在没有其他选项时再考虑。

用户代理字符串最受争议的地方就是在很长一段时间里,浏览器都通过在用户代理字符串包含错误或误导性信息来欺骗服务器。要理解背后的原因,必须回顾一下自 Web 出现之后用户代理字符串的历史。

用户代理的历史

浏览器分析

想要知道自己代码运行在什么浏览器上,大部分开发者会分析 window.navigator.userAgent返回的字符串值。所有浏览器都会提供这个值,如果相信这些返回值并基于给定的一组浏览器检测这个字符串,最终会得到关于浏览器和操作系统的比较精确的结果。

相比于能力检测,用户代理检测还是有一定优势的。能力检测可以保证脚本不必理会浏览器而正常执行。现代浏览器用户代理字符串的过去、现在和未来格式都是有章可循的,我们能够利用它们准确识别浏览器。

1. 伪造用户代理

通过检测用户代理来识别浏览器并不是完美的方式,毕竟这个字符串是可以造假的。只不过实现window.navigator 对象的浏览器(即所有现代浏览器)都会提供 userAgent 这个只读属性。因此简单地给这个属性设置其他值不会有效:

console.log(window.navigator.userAgent);
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.181 Safari/537.36
window.navigator.userAgent = 'foobar';
console.log(window.navigator.userAgent);
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.181 Safari/537.36

不过,通过简单的办法可以绕过这个限制。比如,有些浏览器提供伪私有的defineGetter方法,利用它可以篡改用户代理字符串:

console.log(window.navigator.userAgent);
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/65.0.3325.181 Safari/537.36
window.navigator.__defineGetter__('userAgent', () => 'foobar');
console.log(window.navigator.userAgent);
// foobar

对付这种造假是一件吃力不讨好的事。检测用户代理是否以这种方式被篡改过是可能的,但总体来看还是一场猫捉老鼠的游戏。

与其劳心费力检测造假,不如更好地专注于浏览器识别。如果相信浏览器返回的用户代理字符串,那就可以用它来判断浏览器。如果怀疑脚本或浏览器可能篡改这个值,那最好还是使用能力检测。

2. 分析浏览器

通过解析浏览器返回的用户代理字符串,可以极其准确地推断出下列相关的环境信息:

  • 浏览器
  • 浏览器版本
  • 浏览器渲染引擎
  • 设备类型(桌面/移动)
  • 设备生产商
  • 设备型号
  • 操作系统
  • 操作系统版本

当然,新浏览器、新操作系统和新硬件设备随时可能出现,其中很多可能有着类似但并不相同的用户代理字符串。因此,用户代理解析程序需要与时俱进,频繁更新,以免落伍。这里推荐一些 GitHub 上维护比较频繁的第三方用户代理解析程序:

  • Bowser
  • UAParser.js
  • Platform.js
  • CURRENT-DEVICE
  • Google Closure
  • Mootools

软件与硬件检测

现代浏览器提供了一组与页面执行环境相关的信息,包括浏览器、操作系统、硬件和周边设备信息。这些属性可以通过暴露在 window.navigator 上的一组 API 获得。不过,这些 API 的跨浏览器支持还不够好,远未达到标准化的程度

识别浏览器与操作系统

浏览器元数据

硬件


小结

客户端检测是 JavaScript 中争议最多的话题之一。因为不同浏览器之间存在差异,所以经常需要根据浏览器的能力来编写不同的代码。客户端检测有不少方式,但下面两种用得最多。

  • 能力检测

在使用之前先测试浏览器的特定能力。例如,脚本可以在调用某个函数之前先检查它是否存在。这种客户端检测方式可以让开发者不必考虑特定的浏览器或版本,而只需关注某些能力是否存在。能力检测不能精确地反映特定的浏览器或版本。

  • 用户代理检测

通过用户代理字符串确定浏览器。用户代理字符串包含关于浏览器的很多信息通常包括浏览器、平台、操作系统和浏览器版本。用户代理字符串有一个相当长的发展史,很多浏览器都试图欺骗网站相信自己是别的浏览器。用户代理检测也比较麻烦,特别是涉及 Opera会在代理字符串中隐藏自己信息的时候。即使如此,用户代理字符串也可以用来确定浏览器使用的渲染引擎以及平台,包括移动设备和游戏机。

在选择客户端检测方法时,首选是使用能力检测。特殊能力检测要放在次要位置,作为决定代码逻辑的参考。用户代理检测是最后一个选择,因为它过于依赖用户代理字符串。

浏览器也提供了一些软件和硬件相关的信息。这些信息通过 screen 和 navigator 对象暴露出来。利用这些 API,可以获取关于操作系统、浏览器、硬件、设备位置、电池状态等方面的准确信息。