JavaScript学习笔记(11):客户端检测

能力检测

由于各个浏览器存在差异,它们可能支持不同的特性,因此可能需要在实际开发中对浏览器的种类或支持的特性进行检测。如果有普适的方案可以选择,应该优先选择,而能力检测应当作为最后的方案。

能力检测

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

简单的能力检测

能力检测的基本模式如下:

1
2
3
if (object.propertyInQuestion) {
// 使用object.propertyInQuestion
}

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

1
2
3
4
5
6
7
8
9
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! ");
}
}

安全的能力检测

之所以说上面的能力检测不够安全,是因为它只检测了能力是否存在,而不检测能力是否符合预期(产生预期的结果),有些时候只使用简单的能力检测可能导致意外的结果。如以下例子:

1
2
3
function isSortable(object) {
return !!object.sort;
}

这就是个典型的简单能力检测,只能知道sort是否存在于对象上。一般来说我们默认我们要使用的对象是规范的——即sort这种一看就是和排序有关的名称,不会被用来作为其他用途,只会被用来做和排序有关的事情,但是这不意味着这个名称存在就说明对象具有排序的方法,它可能被设计为一个布尔值用来表示对象是否可排序,此时上述isSortable()函数会返回true,但不代表它的sort是一个能被我们调用的排序方法。

此时应当使用typeof来测试sort是否是一个function才更加保险。

有一个要注意的地方是,我们如果要检测document.createElement()是否可用,按照安全检测的思路,我们应该判断它是否是一个函数,但这在IE8及以下版本浏览器中是不可以的,因为在IE8及以下版本,它是Object而不是Function。因此进行能力检测要充分了解各个浏览器特性才可以考虑完善

浏览器分析

基于能力检测的浏览器分析就是使用能力检测来判断用户正在使用的是什么浏览器,这要求我们能够足够地了解各个浏览器之间的差异。但是现在我们有足够多的轮子来完成这一工作,因此这里不必过多了解。

这是一个使用能力检测进行浏览器分析的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class BrowserDetector {
constructor() {
// 测试条件编译
// IE6~10 支持
this.isIE_Gte6Lte10 = /*@cc_on! @*/false;
// 测试documentMode
// IE7~11 支持
this.isIE_Gte7Lte11 = !!document.documentMode;
// 测试StyleMedia构造函数
// Edge 20 及以上版本支持
this.isEdge_Gte20 = !!window.StyleMedia;
// 测试Firefox专有扩展安装API
// 所有版本的Firefox都支持
this.isFirefox_Gte1 = typeof InstallTrigger !== 'undefined';
// 测试chrome对象及其webstore属性
// Opera的某些版本有window.chrome,但没有window.chrome.webstore
// 所有版本的Chrome都支持
this.isChrome_Gte1 = !!window.chrome && !!window.chrome.webstore;
// Safari早期版本会给构造函数的标签符追加"Constructor"字样,如:
// window.Element.toString(); // [object ElementConstructor]
// Safari 3~9.1 支持
this.isSafari_Gte3Lte9_1 = /constructor/i.test(window.Element);
// 推送通知API暴露在window对象上
// 使用默认参数值以避免对undefined调用toString()
// Safari 7.1 及以上版本支持
this.isSafari_Gte7_1 =
(({
pushNotification = {}
} = {}) =>
pushNotification.toString() == '[object SafariRemoteNotification]'
)(window.safari);
// 测试addons属性
// Opera 20 及以上版本支持
this.isOpera_Gte20 = !!window.opr && !!window.opr.addons;
}
isIE() {
return this.isIE_Gte6Lte10 || this.isIE_Gte7Lte11;
}
isEdge() {
return this.isEdge_Gte20 && !this.isIE();
}
isFirefox() {
return this.isFirefox_Gte1;
}
isChrome() {
return this.isChrome_Gte1;
}
isSafari() {
return this.isSafari_Gte3Lte9_1 || this.isSafari_Gte7_1;
}
isOpera() {
return this.isOpera_Gte20;
}
}

用户代理检测

用户代理检测通过浏览器的用户代理字符串确定使用的是什么浏览器。用户代理字符串包含在每个HTTP请求的头部,在JavaScript中可以通过navigator.userAgent访问。

用户代理

在服务器端,常见的做法是根据接收到的用户代理字符串确定浏览器并执行相应操作。而在客户端,用户代理检测被认为是不可靠的,只应该在没有其他选项时再考虑。因为用户代理字符串常常带有欺骗性信息。

HTTP规范(1.0和1.1)要求浏览器应该向服务器发送包含浏览器名称和版本信息的简短字符串。各个浏览器都有属于自己格式的这个字符串。

浏览器分析

上述用户代理字符串即window.navigator.userAgent返回的字符串值,所有浏览器都会提供这个值。

如果相信这些返回值并基于给定的一组浏览器检测这个字符串,最终会得到关于浏览器和操作系统的比较精确的结果。并且现代浏览器越来越规范,已经很少有伪装的情况出现了,因此使用代理字符串来进行浏览器分析会有比能力检测更大的优势。

一般来说,使用用户代理字符串可以获得如下信息:

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

但由于新系统、新设备、新浏览器层出不穷,要想得到可靠的结果,就要求用户代理解析程序也要与时俱进才行。此时我们也一般会选择成熟的轮子来进行这些工作。

软硬件检测

现代浏览器提供了一组与页面执行环境相关的信息,包括浏览器、操作系统、硬件和周边设备信息。这些属性可以通过暴露在window.navigator上的一组API获得。

强烈建议在使用这些API之前先检测它们是否存在,因为其中多数都不是强制性的,且很多浏览器没有支持。并且这些内容并不一定可靠,因此只做简单介绍,简单了解即可。

浏览器与操作系统

  • navigator.oscpu属性:一个字符串,通常对应用户代理字符串中操作系统/系统架构相关信息。
  • navigator.vendor属性:一个字符串,通常包含浏览器开发商信息。
  • navigator.platform属性:一个字符串,通常表示浏览器所在的操作系统。
  • screen.colorDepthscreen.pixelDepth属性:显示器每像素颜色的位深。
  • screen.orientation属性:一个ScreenOrientation对象,其中包含Screen Orientation API定义的屏幕信息。

浏览器元数据

位置信息

navigator.geolocation属性暴露了Geolocation API,可以让浏览器脚本感知当前设备的地理位置(仅HTTPS下可用)。这个API可以查询宿主系统并尽可能精确地返回设备的位置信息。根据宿主系统的硬件和配置,返回结果的精度可能不一样。

navigator.geolocation返回一个Geolocation对象,这个对象上有getCurrentPosition()方法可以获取用户地理位置,它需要一个回调函数作为参数,如:

1
2
let p;
navigator.geolocation.getCurrentPosition((position) => p = position);

此时的p就是一个GeolocationPosition对象,可以从中获取时间戳、经纬度等信息:

1
2
3
4
5
6
7
8
p.timestamp // 时间戳
p.coords.latitude // 纬度
p.coords.longitude // 经度
p.coords.accuracy // 精度(单位:米)
p.coords.altitude // 高度(海拔)
p.coords.altitudeAccuracy // 海拔精度(单位:米)
p.coords.speed // 设备移动速度
p.coords.heading // 朝向(以正北方为0°的角度)

由于设备的原因,这些数据可能不被支持。

获取浏览器地理位置并不能保证成功。因此getCurrentPosition()方法也接收失败回调函数作为第二个参数,这个函数会收到一个PositionError对象。

网络状态

浏览器可以追踪网络状态,提供网络连接信息的方式有连接事件和navigator.onLine属性。

连接事件即online事件和offline事件。在设备连接到网络时,浏览器会在window对象上触发online事件;当设备断开网络连接后,浏览器会在window对象上触发offline事件。

此外任何时候都可以通过navigator.onLine属性来确定浏览器的联网状态,这个属性是一个布尔值表示浏览器是否联网。

但判断联网状态不一定可靠,有的浏览器觉得连到了局域网就算联网了,不管是否连接到了互联网。

navigator对象还暴露了NetworkInformation API,可以通过navigator.connection属性访问到一个NetworkInformation对象,它的以下属性反映了当前的网络状态:

  • downlink:一个不那么精确的整数,表示当前网络的带宽,单位为Mbit/s。
  • effectiveType:字符串,如”4g”,标识了网络速率的范围。
  • rtt:当前网络往返时间。

以上属性并不一定被全部提供,也可能在它们之外提供了更多属性。

电池状态

navigator.getBattery()方法会返回一个期约实例,解决为一个BatteryManager对象:

1
2
let b;
navigator.getBattery().then((bs) => b = bs);

这里的b即为BatteryManager对象,它有以下属性:

  • charging:布尔值,是否在充电。
  • chargingTime:距离电量耗尽还有多少秒。如果当前没有电池或没有使用电池,则这个值为Infinity。
  • level:0.0到1.0之间的值,表示电量百分比。如果没有电池,始终返回1.0。

BatteryManager对象还有一些事件,可以给它们添加事件监听器或者直接给它们的事件属性赋值以监听事件。事件如下:

  • onchargingchange:充电状态改变时触发。
  • onchargingtimechange:剩余使用时间改变时触发。
  • ondischargingtimechange:放电时间改变时触发。
  • onlevelchange:电量百分比变化时触发。

硬件

硬件检测的内容就相当有限了,主要有以下内容:

  • navigator.hardwareConcurrency属性:返回浏览器支持的逻辑处理器核心数量,包含表示核心数的一个整数值(如果核心数无法确定,这个值就是1)。这个值表示浏览器可以并行执行的最大工作线程数量,不一定是实际的CPU核心数。
  • navigator.deviceMemory属性:返回设备大致的系统内存大小,包含单位为GB的浮点数。
  • navigator.maxTouchPoints属性:返回触摸屏支持的最大关联触点数量,包含一个整数值。非触屏设备返回0。



* 你好,我是大森。如果文章内容帮到了你,你可通过下方付款二维码支持作者 *