曾經,瀏覽器類型嗅探技術被戲稱爲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
可是,事實上——特性檢測也不是徹底可依賴的——他們有時候也會失敗。讓咱們來看一些例子,來了解咱們如何處理每一個事例。瀏覽器
或許,作特性檢測失敗最廣爲認知的事例是,在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 } }
屬性映射常常用來測試瀏覽器對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事件就會被採用。可是觸控屏幕的筆記本該怎麼辦?用戶可能觸控屏幕,或者可能用鼠標或觸控板。上邊的代碼沒法處理這種狀況,因此用鼠標點擊根本沒有做用。
解決方法就是根本不用檢查瀏覽器對事件類型的支持——取而代之,綁定這些事件包括touch與click,而後採用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/