JavaScript >> Javascript 文檔 >  >> JavaScript

特徵檢測不是瀏覽器檢測

長期以來,瀏覽器檢測一直是 Web 開發中的熱門話題。這場戰鬥比 JavaScript 瀏覽器檢測早了幾年,並從 Netscape Navigator 的推出開始,這是第一個真正流行和廣泛使用的 Web 瀏覽器。 Netscape Navigator 2.0 遠遠超出了任何其他可用的網絡瀏覽器,以至於網站在返回任何有用的內容之前開始尋找其特定的用戶代理字符串。這迫使其他瀏覽器供應商,尤其是微軟,在他們的用戶代理字符串中包含一些東西來繞過這種形式的瀏覽器檢測。確實是一個非常棘手的情況。

特徵檢測

從那時起,Web 開發人員反复抱怨瀏覽器檢測,特別是用戶代理嗅探,是一種不應該考慮的可怕做法。爭論是代碼不是“面向未來的”,當新的瀏覽器出現時必須更改。優選的方法,合唱迴聲,是特徵檢測。所以不要做這樣的事情:

if (navigator.userAgent.indexOf("MSIE 7") > -1){
    //do something
}

你應該這樣做:

if(document.all){
    //do something
}

這兩種方法是有區別的。第一個是按名稱和版本測試特定瀏覽器,而第二個是測試特定功能/功能。因此,用戶代理嗅探會導致知道正在使用的確切瀏覽器和版本(或至少是瀏覽器報告的版本),而特徵檢測則確定給定對像或方法是否可用。請注意,這是兩個完全不同的結果。

因為功能檢測不依賴於正在使用的瀏覽器的知識,只依賴於哪些功能可用,因此確保在新瀏覽器中的支持是微不足道的。例如,在 DOM 還很年輕的時候,並不是所有的瀏覽器都支持 getElementById() ,所以有很多這樣的代碼:

if(document.getElementById){  //DOM
    element = document.getElementById(id);
} else if (document.all) {  //IE
    element = document.all[id];
} else if (document.layers){  //Netscape < 6
    element = document.layers[id];
}

這是對特徵檢測的一種很好且適當的使用,因為代碼會測試一個特徵,然後,如果它存在,就使用它。這段代碼最好的部分是,隨著其他瀏覽器開始實現 getElementById() ,代碼不必更改;對新瀏覽器的支持是通過特徵檢測得到的。

混搭

在某些地方,許多 Web 開發人員對這兩種方法之間的區別感到困惑。開始編寫類似這樣的代碼:

//AVOID!!!
if (document.all) {  //IE
    id = document.uniqueID;
} else {
    id = Math.random();
}

這段代碼的問題是對 document.all 的測試 用作 IE 的隱式檢查。一旦知道瀏覽器是IE,就假設使用document.uniqueID是安全的 ,這是特定於 IE 的。但是,您測試的只是 document.all 是否存在,而不是瀏覽器是否為 IE。只是因為 document.all 存在並不意味著 document.uniqueID 也可用。有一個錯誤的暗示會導致代碼中斷。

為了更清楚地說明這個問題,人們開始像這樣替換代碼:

var isIE = navigator.userAgent.indexOf("MSIE") > -1;

使用這樣的代碼:

var isIE = !!document.all;

進行此更改表明對“不要使用用戶代理嗅探”的誤解。您不是在尋找特定的瀏覽器,而是在尋找一項功能,然後嘗試推斷 它是一個特定的瀏覽器,這同樣糟糕。這稱為基於特徵的瀏覽器檢測,是一種非常糟糕的做法。

在某個地方,開發人員意識到 document.all 事實上,這並不是確定瀏覽器是否為 Internet Explorer 的最佳方法。然後你開始看到這樣的代碼:

var isIE = !!document.all && document.uniqueID;

這種方法屬於“太聰明”的編程類別。您通過描述越來越多的識別方面來努力識別某些東西。更糟糕的是,沒有什麼能阻止其他瀏覽器實現相同的功能,這最終會使這段代碼返回不可靠的結果。

如果您認為此類代碼沒有被廣泛使用,請再想一想。以下片段來自 MooTools 1.1.2(注意,當前版本是 1.1.4,所以這是來自舊版本):

//from MooTools 1.1.2
if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
else if (document.childNodes && !document.all && !navigator.taintEnabled) window.webkit = window[window.xpath ? 'webkit420' : 'webkit419'] = true;
else if (document.getBoxObjectFor != null || window.mozInnerScreenX != null) window.gecko = true;

請注意代碼如何嘗試根據功能檢測來確定正在使用的瀏覽器。除了哲學問題,我可以指出任何數量的問題,但最明顯的是 window.ie 將 IE 8 報告為 IE 7。大問題。

為什麼這不起作用?

要了解為什麼基於特徵的瀏覽器檢測不起作用,您只需回顧高中數學課,邏輯語句通常作為幾何的一部分教授。邏輯語句由假設 (p) 和結論 (q) 組成,形式為“if p then q”。您可以嘗試更改陳述形式以確定真相。修改語句的三種方式:

  • 反過來:如果 q 那麼 p
  • 逆:如果不是 p 則不是 q
  • 反正例:如果不是 q 則不是 p

語句的各種形式之間有兩個重要的關係。如果原命題為真,則逆命題也為真。例如,如果最初的陳述是“如果它是一輛汽車,那麼它有輪子”(這是真的),那麼相反的“如果它沒有輪子,那麼它就不是一輛汽車”也是正確的。

第二個關係是在逆和逆之間,所以如果一個是真的,那麼另一個也一定是真的。這在邏輯上是有道理的,因為逆反之間的關係與正反之間的關係是一樣的。

也許比這兩種關係更重要的是不存在的關係。如果原始陳述為真,則不能保證反之亦然。這就是基於特徵的瀏覽器檢測分崩離析的地方。考慮一下真實的陳述,“如果是 Internet Explorer,則實現 document.all。”相反,“如果 document.all 沒有實現,那麼它就不是 Internet Explorer”也是正確的。相反,“如果實現了 document.all,那麼它就是 Internet Explorer”並非嚴格意義上的正確(例如,Opera 實現了它)。基於特徵的檢測假設反之總是正確的,而實際上並不存在這種關係。

在結論中添加更多部分也無濟於事。再次考慮一下這句話,“如果是汽車,那麼它就是有輪子的。”反過來顯然是錯誤的,“如果它有輪子,那麼它就是一輛汽車”。你可以試著讓它更精確:“如果它是一輛汽車,那麼它就有輪子並且需要燃料。”反過來看:“如果它有輪子並且需要燃料,那麼它就是汽車。”也不正確,因為飛機符合該描述。所以再試一次:“如果是汽車,那麼它有輪子,需要燃料,並且使用兩個車軸。”再一次,反過來又不是真的。

這個問題是人類語言的基礎:很難用一組單一的方面來定義整體。我們有“汽車”這個詞,因為它暗示了很多方面,否則我們必須列出這些方面來識別你開車上班的東西。試圖通過命名越來越多的功能來識別瀏覽器是完全相同的問題。你會接近,但它永遠不會是一個可靠的分類。

後果

MooTools 通過選擇基於功能的瀏覽器檢測來支持他們自己和他們的用戶。 Mozilla 從 Firefox 3 開始就警告說,getBoxObjectFor() 方法已被棄用,將在未來的版本中刪除。由於 MooTools 依賴此方法來確定瀏覽器是否基於 Gecko,因此 Mozilla 在即將發布的 Firefox 3.6 版本中刪除此方法意味著運行舊版本 MooTools 的任何人的代碼都可能受到影響。這促使 MooTools 發出升級到最新版本的調用,該版本已“修復”問題。解釋:

奇怪的是,快速瀏覽一下 MooTools 1.2.4 仍然顯示使用 getBoxObjectFor() 進行基於特徵的瀏覽器檢測 :

//from MooTools 1.2.4
var Browser = $merge({

	Engine: {name: 'unknown', version: 0},

	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},

	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},

	Plugins: {},

	Engines: {

		presto: function(){
			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
		},

		trident: function(){
			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
		},

		webkit: function(){
			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
		},

		gecko: function(){
			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
		}

	}

}, Browser || {});

getBoxObjectFor()的用法 略有不同。實際上,該方法已從使用相反的方法變為使用對立的方法。這種變化的問題是你只能積極地不能 識別瀏覽器。再一次,測試新刪除的方法並沒有真正的幫助。

做什麼?

基於特徵的瀏覽器檢測是一種非常糟糕的做法,應該不惜一切代價避免。直接特徵檢測是一種最佳實踐,幾乎在所有情況下,這正是您所需要的。通常,您只需要在使用某個功能之前了解它是否已實現。不要試圖推斷特徵之間的關係,因為你最終會得到誤報或漏報。

我不會說永遠不要使用基於用戶代理嗅探的瀏覽器檢測,因為我確實相信有有效的用例。然而,我不相信有很多有效的用例。如果您正在考慮用戶代理嗅探,請記住這一點:這樣做的唯一安全方法是針對特定瀏覽器的特定版本。如果範圍的上限是瀏覽器的最新版本,則嘗試檢測一系列瀏覽器版本是危險的、脆弱的並且可能會中斷。還建議針對不是最新版本的特定版本 .為什麼?因為您想識別差異,而最簡單的方法是回顧以前的版本,而不是試圖展望不存在的未來版本。這也有助於保護您的代碼免受未來的影響。目標應該始終是編寫在未知瀏覽器開始運行時不會中斷的代碼。

注意:如果您正在考慮用戶代理嗅探,我不建議您擔心用戶代理欺騙。您應該始終準確地尊重瀏覽器作為用戶代理報告的內容。我的做法一直是,如果你告訴我你是 Firefox,我希望你表現得像 Firefox。如果瀏覽器將自己標識為 Firefox 並且不像 Firefox,那不是你的錯。嘗試對報告的用戶代理字符串進行二次猜測是沒有意義的。

因此,建議始終盡可能使用特徵檢測。如果不可能,則回退到用戶代理嗅探瀏覽器檢測。永遠不要使用基於功能的瀏覽器檢測,因為您將被無法維護的代碼所困擾,並且隨著瀏覽器的不斷發展,您將不斷需要更新和更改。

道歉

當我第一次開始寫這篇文章時,我真的不想選擇 MooTools。它恰好為其他開發人員提供了一個非常好的學習機會。 MooTools 開發人員是聰明人,我相信他們會繼續努力改進他們的庫並積極支持他們的龐大用戶群。我們都經歷了相似的學習曲線,我們都可以互相學習。


Tutorial JavaScript 教程
  1. 添加 Github Actions CI 工作流程

  2. 如何使用 Node Js Feathers 框架構建 REST API

  3. 10 個 jQuery 頁面剝離插件

  4. 在線學習 JavaScript

  5. 如何在文本更改上添加淡入過渡

  6. 軟件不是魔法;軟件是人做的

  7. GitHub API 身份驗證 - 個人訪問令牌

  1. Vue Js 2 - 密碼生成器 (vue04)

  2. 構建應用程序時出現react-native-hms-location 錯誤

  3. 事件對像在此代碼中如何工作

  4. 從 JS Promise 計算價值

  5. Angular 和 Django 集成到一個項目中

  6. [已解決] 如何在 NodeJS 和 ExpressJS 上使用 mongoose 同步查詢

  7. 什麼是模板文字以及為什麼要使用它們?

  1. 帶有 React 和 SCSS 的暗模式

  2. API的定義

  3. 在頁面上顯示所有 JavaScript 的 2 個選項

  4. GitHub 學生開發包:獲得 10000 美元以上的好處