瀏覽器檢測

曾經,瀏覽器類型嗅探技術被戲稱爲javascript程序員的「股票交易」。假若咱們知道一些可以在IE5中生效但卻沒法在Netscape4運行的技術,咱們首先作瀏覽器類型嗅探,而後根據不一樣的瀏覽器的兼容性來寫代碼。好比:javascript

if(navigator.userAgent.indexOf('MSIE 5') != -1)
{
    //we think this browser is IE5
}

然而,各個瀏覽器廠家競爭日益激烈,他們在user-agent字段裏增添額外的值,來確保該瀏覽器爲該廠家擁有全部權。這是Mac版本Safari5的user-agent:html

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.59.10 (KHTML, like Gecko) Version/5.1.9 Safari/534.59.10html5

這個字符串能夠匹配「safari」、「webkit」與「KHTML」(Webkit構建基礎——Konqueror codebase),可是它也可以匹配到「Gecko」(firefox的渲染引擎),還有「Mozilla」(因爲歷史緣由,幾乎全部瀏覽器都聲明爲Mozilla)java

增長這些值的目的是爲了規避瀏覽器嗅探技術。假如一個腳本聲明只有firefox可以處理一個特別的功能,它也有可能在其餘瀏覽器如safari可以運行。另外不要忘記用戶本身也能夠改變user-agent——好比我把個人瀏覽器設置成「 Googlebot/1.0」,因此我能夠訪問站長認爲僅僅搜索引擎爬蟲能夠訪問到的內容!程序員

所以,一段時間以後,這種瀏覽器嗅探技術成爲一種沒法實現的困擾,已經大大地失去了做用,另外一種更好的方法閃亮登場——特性檢測web

特性檢測簡單地檢查咱們須要用到的特性。舉個栗子,若是咱們須要getBoundingClientRect(獲取元素相對瀏覽器可見視窗的位置),那麼要作的重要事情就是瀏覽器是否支持這個屬性;因此。咱們不該該檢測瀏覽器類型,而是作特性自己的檢測:ajax

if(typeof document.documentElement.getBoundingClientRect != "undefined")
{
    //the browser supports this function
}

不支持該特性功能的瀏覽器會返回「undefined」類型,因此不會進入條件。即便不經過咱們在指定的瀏覽器測試這腳本,咱們也知道它要麼運行成功,要麼靜默失敗。chrome

或許咱們還能夠作...

可是,事實上——特性檢測也不是徹底可依賴的——他們有時候也會失敗。讓咱們來看一些例子,來了解咱們如何處理每一個事例。瀏覽器

ActiveX object

或許,作特性檢測失敗最廣爲認知的事例是,在IE中檢測ActiveXObject來作ajax請求安全

ActiveX 屬於晚綁定對象,具體意思就是在嘗試用ActiveX以前,你是沒法肯定
瀏覽器是否支持這個特性。因此,若是用戶禁止了ActiveX,下面的代碼會拋出錯誤:

if(typeof window.ActiveXObject != "undefined")
{
    var request = new ActiveXObject("Microsoft.XMLHTTP");
}

爲了解決這個問題,咱們須要作異常處理——根據不一樣狀況嘗試實例化對象,捕捉失敗:

if(typeof window.ActiveXObject != "undefined")
{
  try
  {
    var request = new ActiveXObject("Microsoft.XMLHTTP");
  }
  catch(ex)
  {
    request = null;
  }
  if(request !== null)
  {
    //... we have a request object
  }
}

HTML屬性( attributes )與DOM屬性的映射(properties)

屬性映射常常用來測試瀏覽器對HTML5 API屬性的支持狀況。好比,經過尋找draggable屬性,檢查帶有[draggable="true"]的元素支持** Drag and Drop API**:

if("draggable" in element)
{
  //the browser supports drag and drop
}

問題是,IE8或者更早的版本,自動創建了全部HTML屬性到DOM屬性的映射關係。這就是getAttribute在老版本IE是如此糟糕的緣由,由於它根本不返回HTML屬性,而是返回一個DOM 屬性。

這意味着,若是咱們用已經存在屬性的元素:

<div draggable="true"> ... </div>

這在IE8或更早的版本中,進行draggable檢測會返回true,即便這些瀏覽器並不支持draggable屬性。

HTML屬性(attribute)能夠是任意的:

<div nonsense="true"> ... </div>

在IE8或更早的版本,結果可想而知,返回爲true

"nonsense" in element

解決方案,檢測沒有屬性(attribute)的元素,最安全的方法就是建立新的元素:

if("draggable" in document.createElement("div"))
{
  //the browser really supports drag and drop
}

對用戶行爲的判斷

你可能遇到過這樣的代碼用來檢測可觸控設備:

if("ontouchstart" in window)
{
  //this is a touch device
}

大多數觸控設備在觸發click事件以前故意設置了一個延遲(一般接近300ms),以便於元素可以觸發double-tapped而不是進行點擊。可是這樣會使應用感到反應遲緩或者無響應。因此,開發者有時採用特性檢測來管理不一樣事件:

if("ontouchstart" in window)
{
  element.addEventListener("touchstart", doSomething);
}
else
{
  element.addEventListener("click", doSomething);
}

然而,這個條件處理來自於一個錯誤的判斷——由於設備只要支持touch,touch事件就會被採用。可是觸控屏幕的筆記本該怎麼辦?用戶可能觸控屏幕,或者可能用鼠標或觸控板。上邊的代碼沒法處理這種狀況,因此用鼠標點擊根本沒有做用。

解決方法就是根本不用檢查瀏覽器對事件類型的支持——取而代之,綁定這些事件包括touchclick,而後採用preventDefault來阻止touch來產生click事件

element.addEventListener("touchstart", function(e)
{
  doSomething();

  e.preventDefault();

}, false);

element.addEventListener("click", function()
{
  doSomething();

}, false);

檢測特性失效帶來的災難

這是能夠容忍的痛苦事情,可是有時候這不是咱們須要的特性的問題——某些瀏覽器聲明支持一些特性,但實際卻不能生效。最近的例子就是Opera12中的setDragImage()(可拖拽對象的方法dataTransfer

這裏特性檢測失敗就是由於Opera12僅是對外聲明支持,卻沒法真正的實現;即便異常處理也不會有做用,由於它不會拋出任何錯誤。它僅僅是不能運行:

//Opera 12 passes this condition, but the function does nothing
if("setDragImage" in e.dataTransfer)
{
  e.dataTransfer.setDragImage("ghost.png", -10, -10);
}

或者,某個瀏覽器開發了某個特性,可是卻有BUG。

面對上述問題,咱們不得不思考:什麼纔是檢測瀏覽器最安全的方法?

我有兩點建議:
1. 採用專有對象(proprietary object)測試 優先於 navigator信息
2. 採用排除法,即不包括該特性的瀏覽器來作特別處理,而不是囊括具備某特性的全部瀏覽器來作特別處理

好比: opera12或者更早的版本能夠檢測到window.opera對象,因此咱們能夠這樣檢測除了opera以外的其餘瀏覽器對draggable的支持狀況:

if(!window.opera && ("draggable" in document.createElement("div")))
{
  //the browser supports drag and drop but is not Opera 12
}

更好的方式,咱們加上瀏覽器專有對象的檢測:

if(window.opera)
{
  //Opera 12 or earlier, but not Opera 15 or later
}
if(document.uniqueID)
{
  //any version of Internet Explorer
}
if(window.InstallTrigger)
{
  //any version of Firefox
}

專有對象能夠採用組合的方式:

if(document.uniqueID && window.JSON)
{
  //IE with JSON (which is IE8 or later)
}
if(document.uniqueID && !window.Intl)
{
  //IE without the Internationalization API (which is IE10 or earlier)
}

咱們已經提過userAgent字符串是不可依賴的,可是vendor字段實際上仍是可判斷的,能夠用來檢測是chrome仍是safari:

if(navigator.vendor == 'Google Inc.')
{
  //any version of Chrome
}
if(navigator.vendor == 'Apple Computer, Inc.')
{
  //any version of Safari (including iOS builds)
}

選擇測試語法

以前,我喜歡測試檢查對象與特性的各類寫法。好比,下面的寫法是最近比較經常使用的:

if("foo" in bar)
{
}

過去咱們不能採用這種寫法是由於IE5時代的瀏覽器會拋出語法錯誤;可是如今咱們不在考慮這個問題,由於咱們不須要在支持這些瀏覽器。

在本質上,它與下面的寫法是等價的:

if(typeof bar.foo != "undefined")
{
}

由於JS能夠進行隱式轉換,因此咱們還能夠這樣寫:

if(foo.bar)
{
}

但這種寫法也會有潛在的問題:foo.bar 爲空字符串或者布爾值false或者數值0的時候,結果就與咱們的預期有差距。舉個例子,*style.maxWidth * 屬性有時會被除了IE6的瀏覽器運用,咱們應該這樣檢測:

if(typeof document.documentElement.style.maxWidth != "undefined")
{
}

maxWidth屬性只會在瀏覽器支持而且做者設定了該值轉換爲true,因此咱們這樣寫檢測就會失敗:

if(document.documentElement.style.maxWidth)
{
}

總結一下主要規則 : 自動類型轉換相對對象與函數是安全可信賴的,可是針對字符串與數值卻不值得信賴。

既然已經說到這:若是你可以安全利用它,那就去作吧。由於它在現代瀏覽器一般比較快。

更多內容,請關注:Automatic Type Conversion In The Real World.

原文地址: http://www.sitepoint.com/javascript-feature-detection-fails/

相關文章
相關標籤/搜索