由于各个浏览器存在差异,它们可能支持不同的特性,因此可能需要在实际开发中对浏览器的种类或支持的特性进行检测。 如果有普适的方案可以选择,应该优先选择 ,而能力检测应当作为最后的方案。
能力检测(又称特性检测)即在JavaScript运行时中使用一套简单的检测逻辑,测试浏览器是否支持某种特性。这种方式 不要求事先知道特定浏览器的信息,只需检测自己关心的能力是否存在即可 。
能力检测的基本模式如下:
123
if (object.propertyInQuestion) { // 使用object.propertyInQuestion}
一个例子:IE5之前的版本中没有 document.getElementById() 这个DOM方法,但可以通过 document.all 属性实现同样的功能。为此,可以进行如下能力检测:
document.getElementById()
document.all
123456789
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! "); }}
之所以说上面的能力检测不够安全,是因为它 只检测了能力是否存在,而不检测能力是否符合预期 (产生预期的结果),有些时候只使用简单的能力检测可能导致意外的结果。如以下例子:
function isSortable(object) { return !!object.sort;}
这就是个典型的简单能力检测,只能知道sort是否存在于对象上。一般来说我们默认我们要使用的对象是规范的——即sort这种一看就是和排序有关的名称,不会被用来作为其他用途,只会被用来做和排序有关的事情,但是这不意味着这个名称存在就说明对象具有排序的方法,它可能被设计为一个布尔值用来表示对象是否可排序,此时上述 isSortable() 函数会返回true,但不代表它的sort是一个能被我们调用的排序方法。
isSortable()
此时应当使用typeof来测试sort是否是一个function才更加保险。
有一个要注意的地方是,我们如果要检测 document.createElement() 是否可用,按照安全检测的思路,我们应该判断它是否是一个函数,但这在IE8及以下版本浏览器中是不可以的,因为在IE8及以下版本,它是Object而不是Function。因此进行 能力检测要充分了解各个浏览器特性才可以考虑完善 。
document.createElement()
基于能力检测的浏览器分析就是使用能力检测来判断用户正在使用的是什么浏览器,这要求我们能够足够地了解各个浏览器之间的差异。但是现在我们有足够多的轮子来完成这一工作,因此这里不必过多了解。
这是一个使用能力检测进行浏览器分析的例子:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
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 访问。
navigator.userAgent
在服务器端,常见的做法是根据接收到的用户代理字符串确定浏览器并执行相应操作。而在客户端,用户代理检测被认为是不可靠的,只应该在没有其他选项时再考虑。因为用户代理字符串常常带有欺骗性信息。
HTTP规范(1.0和1.1)要求浏览器应该向服务器发送包含浏览器名称和版本信息的简短字符串。各个浏览器都有属于自己格式的这个字符串。
上述用户代理字符串即 window.navigator.userAgent 返回的字符串值,所有浏览器都会提供这个值。
window.navigator.userAgent
如果相信这些返回值并基于给定的一组浏览器检测这个字符串,最终会得到关于浏览器和操作系统的比较精确的结果。并且现代浏览器越来越规范,已经很少有伪装的情况出现了,因此使用代理字符串来进行浏览器分析会有比能力检测更大的优势。
一般来说,使用用户代理字符串可以获得如下信息:
但由于新系统、新设备、新浏览器层出不穷,要想得到可靠的结果,就要求用户代理解析程序也要与时俱进才行。此时我们也一般会选择成熟的轮子来进行这些工作。
现代浏览器提供了一组与页面执行环境相关的信息,包括浏览器、操作系统、硬件和周边设备信息。这些属性可以通过暴露在 window.navigator 上的一组API获得。
window.navigator
强烈建议在使用这些API之前先检测它们是否存在,因为其中多数都不是强制性的,且很多浏览器没有支持。并且这些内容 并不一定可靠 ,因此只做简单介绍,简单了解即可。
浏览器与操作系统
navigator.oscpu
navigator.vendor
navigator.platform
screen.colorDepth
screen.pixelDepth
screen.orientation
navigator.geolocation 属性暴露了Geolocation API,可以让浏览器脚本感知当前设备的地理位置(仅HTTPS下可用)。这个API可以查询宿主系统并尽可能精确地返回设备的位置信息。根据宿主系统的硬件和配置,返回结果的精度可能不一样。
navigator.geolocation
navigator.geolocation 返回一个Geolocation对象,这个对象上有 getCurrentPosition() 方法可以获取用户地理位置,它需要一个回调函数作为参数,如:
getCurrentPosition()
12
let p;navigator.geolocation.getCurrentPosition((position) => p = position);
此时的p就是一个GeolocationPosition对象,可以从中获取时间戳、经纬度等信息:
12345678
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 属性。
navigator.onLine
连接事件即online事件和offline事件。在设备连接到网络时,浏览器会在window对象上触发online事件;当设备断开网络连接后,浏览器会在window对象上触发offline事件。
此外任何时候都可以通过 navigator.onLine 属性来确定浏览器的联网状态,这个属性是一个布尔值表示浏览器是否联网。
但判断联网状态 不一定可靠 ,有的浏览器觉得连到了局域网就算联网了,不管是否连接到了互联网。
navigator对象还暴露了NetworkInformation API,可以通过 navigator.connection 属性访问到一个NetworkInformation对象,它的以下属性反映了当前的网络状态:
navigator.connection
以上属性并不一定被全部提供,也可能在它们之外提供了更多属性。
navigator.getBattery() 方法会返回一个期约实例,解决为一个BatteryManager对象:
navigator.getBattery()
let b;navigator.getBattery().then((bs) => b = bs);
这里的b即为BatteryManager对象,它有以下属性:
BatteryManager对象还有一些事件,可以给它们添加事件监听器或者直接给它们的事件属性赋值以监听事件。事件如下:
硬件检测的内容就相当有限了,主要有以下内容:
navigator.hardwareConcurrency
navigator.deviceMemory
navigator.maxTouchPoints