学习目标
- 使用能力检测
- 用户代理检测的历史
- 选择检测的方式
各大浏览器在实现公共接口方面投入了很多精力,但是结果仍然是每一种浏览器都有各自存在不一致性的问题,面对普遍不一致的问题,开发人员要么采取迁就各方的“最小公分母”策略,要么就得利用各种客户端的检测方法,来突破或者规避种种局限。
最常用也是最为人们广泛接受的客户端检测形式是能力检测,能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力,采用这种形式不必顾忌特定的浏览器是如何如何,只要确定浏览器支持的特定的能力,就可以给出解决方案,举个例子。
在IE5之前的版本不支持document.getElementById()这个DOM方法,尽管可以使用非标准的document.all属性实现相同的功能,但是IE早期的版本中,确实不存在前面那个方法,于是就有了下面的能力检测代码。
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!')
}
}
这里的getElement函数的用途是返回具有给定ID的元素,因为document.getElementById是实现这一目的的标准,所以一开始就检测了这个方法,如果该函数不存在就继续监测document.all是否存在,如果是就使用,如果两个特性都不满足,则创建一个错误并抛出。表示这个函数没有办法使用
主要要理解能力检测,首先必须要理解两个重要的概念。
-
先检测达成目的的最常用的特性,可以保证代码最优化。因为在多数情况下,都可以避免测试多个条件。
-
必须检测实际要用到的特性,一个特性存在并不意味着另外一个特性也存在。
function getWindowWith () {
if (document.all) {
return document.documentElement.clientWidth // 错误的使用例子
} else {
return window.innerWidth
}
}
上面是一个错误使用能力检测的例子,检测document.all是否存在,并不意味着document.documentElement.clientWith也存在。
能力检测对于想知道某个特性是否会按照适当的方式行事(而不仅仅是某个特性存在)非常有用。
function isSortable (object) {
return !!object.sort
}
这个函数通过检测对象是否存在sort方法,来确定是否支持排序,问题是包含sort属性的对象也会返回true
let result = isSortable({ sort: true })
检测某个属性是否存在并不能确定对象是够支持排序,更好的方式是检测sort是不是一个函数。
function isSortable (object) {
return typeof object.sort === 'function'
}
在可能的情况下,要尽量用typeof进行能力检测,特别是,宿主对象没有义务让typeof返回合理的值,最令人发指的是事就发生在ie中,大多数浏览器在检测到document.createElement()
存在时多会返回true
function hasCreateElement () {
return typeof document.createElement === 'function'
}
但是在ie8及其之前的版本中,这个函数返回false,因为typeof document.createElement
返回的是object
而不是function
,本质原因是DOM对象是宿主对象,IE及其更早的版本中的宿主对象是COM对象,所以typeof才会返回object,IE9纠正了这个问题,对所有的DOM方法都返回function
.
再举个例子了解一下typeof的的行为不准确性。
ActiveX对象(只有IE支持)与其他对象的行为差异很大。
let xhr = new ActiveXObject('Microsoft.XMLHttp')
if (xhr.open) {
// xxx
}
直接把函数当做属性访问会导致js错误,所以使用typeof操作符会更加安全一些。但是有一个问题是typeof xhr.open会返回unknow
检测某个或者某几个特性并不能确定浏览器,下面的代码就是错误地依赖能力检测的典型示例。
let isFirefox = !!(navigator.vendor && navigator.vendorSub)
let isIE = !!(document.all && document.uniqueID)
这两行代码代表了对能力检测的典型勿用,以前确实可以通过检测navigatorv.vendor和navigator.vendorSub来确定firefox浏览器,但是别的浏览器也会实现相同的功能,所以检测自然会出现问题。
9.2 怪癖检测
与能力检测类似,怪癖检测的目标是识别浏览器的特殊行为,但与能力检测确认浏览器支持什么能力不同,怪癖检测是想要知道浏览器存在什么缺陷,这通常需要运行一段代码,以确定某一特性不能正常工作。
- IE8之前有一个bug,即如果某个实例属性与标记为[[DontEnum]]的某个原型属性同名,name该实例属性将不会出现在for in循环当中。使用以下代码来检测。
let hasDontEnumQuick = (() => {
let o = { toString () {} }
for (let prop in o) {
if (prop === 'toString') {
return false
}
}
return true
})()
- Safari3以前的浏览器版本会枚举被隐藏的属性,可以用下面的代码检测该怪癖
let hasEnumShadowQuick = (() => {
let o = { toString () {} }
let count = 0
for (let prop in o) {
if (prop === 'toString') {
count++
}
}
return count > 1
})()
如果浏览器存在这个bug,那么for in循环枚举带有自定义的toString方法的对象,就会返回两个toString 的实例。
一般来说“怪癖”检测是个别浏览器所独有的,而且通常被归为bug,建议仅检测那些对你有直接影响的怪癖,而且最好是在脚本一开始就执行此类检测,以便尽早解决问题
第三种方式,也是争议最大的一种客户端检测方式叫做用户代理检测。用户代理检测通过检测用户代理字符串来确定实际使用的浏览器。 因为存在浏览器通过再自己的用户代理字符串中添加一些错误或者误导的信息来达到欺骗的目的。其优先级排在能力检测和怪癖检测之后。