處理常見的 JavaScript 問題
現在,我們將研究常見的跨瀏覽器 JavaScript 問題以及如何修復它們。這包括使用瀏覽器開發者工具來追蹤和修復問題,使用 Polyfills 和庫來解決問題,讓現代 JavaScript 功能在舊版瀏覽器中執行等等。
| 先決條件 | 熟悉核心 HTML、CSS 和 JavaScript 語言;瞭解 跨瀏覽器測試原則 的高階概念。 |
|---|---|
| 目標 | 能夠診斷常見的 JavaScript 跨瀏覽器問題,並使用合適的工具和技術來修復它們。 |
JavaScript 的問題
從歷史上看,JavaScript 充斥著跨瀏覽器相容性問題——早在 20 世紀 90 年代,當時的瀏覽器主要選擇(Internet Explorer 和 Netscape)的指令碼是用不同的語言實現的(Netscape 有 JavaScript,IE 有 JScript,並且還提供 VBScript 作為選擇),儘管 JavaScript 和 JScript 在一定程度上是相容的(都基於 ECMAScript 規範),但它們經常以衝突、不相容的方式實現,給開發者帶來了許多噩夢。
這種不相容性問題一直持續到 21 世紀初,因為舊版瀏覽器仍在使用,並且仍然需要支援。例如,建立 XMLHttpRequest 物件的程式碼必須對 Internet Explorer 6 進行特殊處理。
if (window.XMLHttpRequest) {
// Mozilla, Safari, IE7+ ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) {
// IE 6 and older
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
這是像 jQuery 這樣的庫出現的其中一個主要原因——為了抽象化瀏覽器實現的差異,以便開發者可以使用,例如 jQuery.ajax(),它會在後臺處理這些差異。
從那時起,情況已經有了顯著改善;現代瀏覽器在支援“經典 JavaScript 功能”方面做得很好,並且隨著對支援舊版瀏覽器的需求減少(儘管請記住,它們並沒有完全消失),對使用此類程式碼的需求也減少了。
如今,大多數跨瀏覽器 JavaScript 問題出現在
- 當低質量的瀏覽器嗅探程式碼、功能檢測程式碼和供應商字首使用阻止瀏覽器執行本可以正常使用的程式碼時。
- 當開發者在程式碼中使用新的/新興的 JavaScript 功能、現代 Web API 等,並發現這些功能在舊版瀏覽器中無法正常工作時。
我們將在下面探討所有這些問題以及更多問題。
修復一般的 JavaScript 問題
正如我們在關於 HTML/CSS 的 上一篇文章 中所說,你應該確保你的程式碼在一般情況下能夠正常工作,然後再開始專注於跨瀏覽器問題。如果你還不熟悉 JavaScript 排錯 的基礎知識,你應該在繼續學習之前學習這篇文章。你需要留意許多常見的 JavaScript 問題,例如
- 基本語法和邏輯問題(同樣,請檢視 JavaScript 排錯)。
- 確保變數等在正確的範圍內定義,並且不會遇到在不同位置宣告的專案之間衝突的問題(請檢視 函式作用域和衝突)。
- 對 this 的混淆,具體來說是指它適用於哪個作用域,以及它的值是否是你想要的。你可以閱讀 什麼是“this”? 獲取一個簡單的介紹;你還應該研究像 這個例子 這樣的示例,它展示了將
this作用域儲存到一個單獨變數中,然後在巢狀函式中使用該變數的典型模式,這樣你就可以確保將功能應用於正確的this作用域。 - 在使用全域性變數進行迭代的迴圈中不正確地使用函式(更一般地說,“作用域錯誤”)。
例如,在 bad-for-loop.html 中(請檢視 原始碼),我們使用用 var 定義的變數迴圈 10 次,每次建立一個段落並向其新增一個 onclick 事件處理程式。單擊時,我們希望每個段落顯示一個包含其編號的警報訊息(建立時 i 的值)。但它們都報告 i 為 11——因為 for 迴圈在巢狀函式被呼叫之前完成所有迭代。
最簡單的解決方案是用 let 而不是 var 宣告迭代變數——與函式關聯的 i 值在每次迭代時都是唯一的。請檢視 good-for-loop.html(請檢視 原始碼)以獲取一個可以正常工作的版本。
- 確保在嘗試使用 非同步操作 返回的值之前,它們已經完成。這通常意味著理解如何使用promise:適當地使用
await或者在 promise 的then()處理程式中執行處理非同步呼叫結果的程式碼。請檢視 如何使用 promise 獲取有關此主題的介紹。
注意:有問題的 JavaScript 程式碼:JavaScript 開發者犯的 10 個最常見錯誤 對這些常見錯誤以及更多錯誤進行了很好的討論。
程式碼檢查器
與 HTML 和 CSS 一樣,你可以使用程式碼檢查器來確保更好的質量,減少 JavaScript 程式碼中的錯誤,程式碼檢查器會指出錯誤,並且還可以標記有關不良實踐等的警告,並且可以自定義以使其在錯誤/警告報告方面更加嚴格或更加寬鬆。我們推薦的 JavaScript/ECMAScript 程式碼檢查器是 JSHint 和 ESLint;它們可以透過多種方式使用,我們將在下面詳細介紹其中一些方式。
線上
JSHint 主頁 提供了一個線上程式碼檢查器,它允許你在左側輸入你的 JavaScript 程式碼,並在右側提供輸出,包括指標、警告和錯誤。
程式碼編輯器外掛
將你的程式碼複製貼上到網頁上多次檢查其有效性很不方便。你真正想要的是一個能夠儘可能少地麻煩地融入你的標準工作流程的程式碼檢查器。許多程式碼編輯器都有程式碼檢查器外掛。例如,請檢視 JSHint 安裝頁面 中的“文字編輯器和 IDE 的外掛”部分。
其他用途
還有其他方式使用這些程式碼檢查器;你可以在 JSHint 和 ESLint 安裝頁面上閱讀有關這些方式的資訊。
值得一提的是命令列用法——你可以使用 npm(Node 包管理器——你需要先安裝 NodeJS)將這些工具安裝為命令列實用程式(可透過 CLI——命令列介面使用)。例如,以下命令會安裝 JSHint
npm install -g jshint
然後,你可以將這些工具指向你想要檢查的 JavaScript 檔案,例如
你還可以將這些工具與任務執行器/構建工具(如 Gulp 或 Webpack)一起使用,以便在開發過程中自動檢查你的 JavaScript 程式碼。(請檢視後面文章中的 使用任務執行器來自動化測試工具。)請檢視 ESLint 整合 以獲取 ESLint 選項;JSHint 預設情況下受 Grunt 支援,並且還有其他整合可用,例如 JSHint 載入器用於 Webpack。
注意:ESLint 比 JSHint 需要更多設定和配置,但它也更強大。
瀏覽器開發者工具
瀏覽器開發者工具有許多有用的功能可以幫助除錯 JavaScript。首先,JavaScript 控制檯會報告程式碼中的錯誤。
製作我們 fetch-broken 示例的本地副本(請檢視 原始碼)。
如果你檢視控制檯,你會看到一條錯誤訊息。確切的措辭取決於瀏覽器,但它應該是類似於:“Uncaught TypeError: heroes is not iterable”的內容,並且引用的行號是 25。如果我們檢視原始碼,相關的程式碼部分是
function showHeroes(jsonObj) {
const heroes = jsonObj["members"];
for (const hero of heroes) {
// ...
}
}
所以程式碼在嘗試使用 jsonObj(正如你可能預期的那樣,它應該是一個 JSON 物件)時就崩潰了。它應該使用以下 fetch() 呼叫從外部 .json 檔案中獲取
const requestURL =
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
const response = fetch(requestURL);
populateHeader(response);
showHeroes(response);
但這失敗了。
控制檯 API
你可能已經知道這段程式碼有什麼問題,但讓我們進一步探索它,展示如何調查這個問題。首先,有一個 控制檯 API,它允許 JavaScript 程式碼與瀏覽器的 JavaScript 控制檯進行互動。它提供了一些功能,但你最常使用的功能是 console.log(),它會將自定義訊息列印到控制檯。
嘗試新增一個 console.log() 呼叫以記錄 fetch() 的返回值,如下所示
const requestURL =
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
const response = fetch(requestURL);
console.log(`Response value: ${response}`);
const superHeroes = response;
populateHeader(superHeroes);
showHeroes(superHeroes);
重新整理瀏覽器中的頁面。這次,在錯誤訊息之前,你會看到一條新訊息被記錄到控制檯
Response value: [object Promise]
console.log() 輸出顯示 fetch() 的返回值不是 JSON 資料,而是一個 Promise。fetch() 函式是非同步的:它返回一個 Promise,只有在從網路收到實際響應後才會實現。在使用響應之前,我們必須等待 Promise 實現。
我們可以透過將使用響應的程式碼放在返回的 Promise 的 then() 方法內部來實現,如下所示
const response = fetch(requestURL);
fetch(requestURL).then((response) => {
populateHeader(response);
showHeroes(response);
});
總而言之,每當出現問題,並且某個值在程式碼中的某個位置看起來不像它應該的那樣時,你都可以使用 console.log() 將其打印出來,看看發生了什麼。
使用 JavaScript 偵錯程式
不幸的是,我們仍然遇到相同的錯誤——問題沒有解決。現在讓我們使用瀏覽器開發者工具中更復雜的功能:JavaScript 偵錯程式(在 Firefox 中的叫法)。
注意:其他瀏覽器中也有類似的工具;Chrome 中的 Sources 選項卡、Safari 中的 Debugger(請檢視 Safari Web 開發工具)等。
在 Firefox 中,Debugger 選項卡如下所示
- 在左側,你可以選擇你想要除錯的指令碼(在本例中我們只有一個)。
- 中央面板顯示了所選指令碼中的程式碼。
- 右側面板顯示了與當前環境相關的有用詳細資訊——斷點、呼叫棧和當前活動的作用域。
這些工具的主要功能是能夠在程式碼中新增斷點——這些是程式碼執行停止的點,並且在那一點上,你可以檢查當前狀態下的環境,看看發生了什麼。
讓我們開始工作。錯誤現在出現在第 26 行。點選中心面板中的第 26 行,為它新增斷點(您會看到一個藍色箭頭出現在它的頂部)。現在重新整理頁面(Cmd/Ctrl + R)——瀏覽器將在第 26 行暫停程式碼執行。此時,右側將更新以顯示一些非常有用的資訊。
- 在 *斷點* 下,您將看到您設定的斷點的詳細資訊。
- 在 *呼叫堆疊* 下,您將看到一些條目——這基本上是導致當前函式被呼叫的函式序列的列表。在頂部,我們有 `showHeroes()`,這是我們當前所在的函式,其次是 `onload`,它儲存了包含對 `showHeroes()` 的呼叫的事件處理程式函式。
- 在 *作用域* 下,您將看到我們正在檢視的函式的當前活動作用域。我們只有三個——`showHeroes`、`block` 和 `Window`(全域性作用域)。每個作用域都可以擴充套件以顯示程式碼執行停止時作用域內變數的值。
我們可以在這裡找到一些非常有用的資訊。
- 展開 `showHeroes` 作用域——您可以從這裡看到 heroes 變數是 `undefined`,這表明訪問 `jsonObj` 的 `members` 屬性(函式的第一行)沒有成功。
- 您還可以看到 `jsonObj` 變數儲存的是一個
Response物件,而不是一個 JSON 物件。
傳遞給 `showHeroes()` 的引數是 `fetch()` promise 被 fulfille 的值。因此,這個 promise 不是 JSON 格式:它是一個 `Response` 物件。還需要一個額外的步驟才能將響應的內容檢索為 JSON 物件。
我們希望您自己嘗試解決這個問題。為了幫助您開始,請檢視 Response 物件的文件。如果您遇到困難,可以在 https://github.com/mdn/learning-area/tree/main/tools-testing/cross-browser-testing/javascript/fetch-fixed 找到修復後的原始碼。
注意:偵錯程式選項卡有許多其他有用的功能,我們在這裡沒有討論,例如條件斷點和觀察表示式。有關更多資訊,請參見 偵錯程式 頁面。
效能問題
隨著應用程式變得越來越複雜,您開始使用更多的 JavaScript,您可能會開始遇到效能問題,尤其是在檢視較慢裝置上的應用程式時。效能是一個很大的話題,我們在這裡沒有時間詳細介紹它。以下是一些快速提示
- 為了避免載入超過需要的 JavaScript,請使用 Browserify 等解決方案將您的指令碼捆綁到單個檔案中。一般來說,減少 HTTP 請求的數量對效能非常有利。
- 在將檔案載入到生產伺服器之前,透過縮小它們來使它們更小。縮小會將所有程式碼壓縮在一起到一個巨大的單行上,使其佔用更少的檔案大小。它很難看,但完成後您不需要閱讀它!最好使用 Uglify 等縮小工具來完成(也有一些線上版本——請參見 JSCompress.com)。
- 在使用 API 時,請確保在未使用 API 功能時將其關閉;某些 API 呼叫在處理能力上可能非常昂貴。例如,在顯示影片流時,請確保在您看不到它時將其關閉。在使用重複的 Geolocation 呼叫跟蹤裝置位置時,請確保在使用者停止使用它時將其關閉。
- 動畫對效能來說可能非常昂貴。許多 JavaScript 庫提供了由 JavaScript 編寫的動畫功能,但透過原生瀏覽器功能(如 CSS 動畫(或新興的 Web 動畫 API))而不是 JavaScript 來執行動畫的成本效益要高得多。閱讀 Brian Birtles 的 Animating like you just don't care with Element.animate,瞭解為什麼動畫很昂貴、如何提高動畫效能以及有關 Web 動畫 API 的資訊。
注意:Addy Osmani 的 Writing Fast, Memory-Efficient JavaScript 包含大量細節和一些提高 JavaScript 效能的出色技巧。
跨瀏覽器 JavaScript 問題
在本節中,我們將介紹一些常見的跨瀏覽器 JavaScript 問題。我們將把它分解成
- 使用現代核心 JavaScript 功能
- 使用現代 Web API 功能
- 使用錯誤的瀏覽器嗅探程式碼
- 效能問題
使用現代 JavaScript/API 功能
在 上一篇文章 中,我們描述了一些由於語言的性質而導致 HTML 和 CSS 錯誤和無法識別的功能的處理方式。然而,JavaScript 不像 HTML 和 CSS 那樣寬鬆——如果 JavaScript 引擎遇到錯誤或無法識別的語法,例如使用新的、不支援的功能時,它往往會丟擲錯誤。
有一些策略可以處理新功能支援;讓我們探索最常見的方法。
注意:這些策略並不存在於單獨的孤島中——您當然可以根據需要將它們組合起來。例如,您可以使用功能檢測來確定當前瀏覽器是否支援某個功能;如果它不支援,您就可以執行程式碼來載入 polyfill 或庫來處理不支援的情況。
功能檢測
功能檢測背後的理念是,您可以執行測試來確定當前瀏覽器是否支援 JavaScript 功能,然後有條件地執行程式碼以在支援和不支援該功能的瀏覽器中提供可接受的體驗。作為一個快速示例,Geolocation API(它公開了執行 web 瀏覽器的裝置的可用位置資料)具有其使用的主要入口點——在全域性 Navigator 物件上可用的 `geolocation` 屬性。因此,您可以使用類似以下內容來檢測瀏覽器是否支援地理定位
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
// show the location on a map, perhaps using the Google Maps API
});
} else {
// Give the user a choice of static maps instead perhaps
}
您還可以為 CSS 功能編寫這樣的測試,例如透過測試 `element.style.property` 的存在(例如 `paragraph.style.transform !== undefined`)。如果您想在支援 CSS 功能的情況下應用樣式,您可以直接使用 @supports at-rule(稱為功能查詢)。例如,要檢查瀏覽器是否支援 CSS 容器查詢,您可以執行以下操作
@supports (container-type: inline-size) {
/* Use container queries if supported */
}
最後一點,不要將功能檢測與瀏覽器嗅探(檢測訪問站點的具體瀏覽器)混淆——這是一種應該不惜一切代價避免的糟糕做法。有關更多詳細資訊,請參見後面的 不要瀏覽器嗅探。
注意:功能檢測將在本模組後面的專用文章中詳細介紹。
庫
JavaScript 庫本質上是您可以附加到頁面的第三方程式碼單元,為您提供豐富的現成功能,可以直接使用,從而節省您大量的時間。許多 JavaScript 庫可能之所以存在,是因為它們的開發人員編寫了一組通用的實用程式函式,以在編寫未來專案時節省時間,並決定將它們釋出到野外,因為其他人也可能覺得它們有用。
JavaScript 庫往往有幾種主要型別(一些庫將服務於多個目的)
- 實用程式庫:提供大量函式,使平凡的任務更容易管理,並且不那麼無聊。例如,jQuery 提供了自己的全功能選擇器和 DOM 操作庫,允許您在 JavaScript 中使用 CSS 選擇器型別的選擇元素,並更輕鬆地構建 DOM。現在我們有了跨瀏覽器可用的現代功能,例如
Document.querySelector()/Document.querySelectorAll()/Node方法,它已經不再那麼重要了,但在支援舊版瀏覽器時它仍然很有用。 - 便利庫:使困難的事情更容易完成。例如,WebGL API 非常複雜,難以直接編寫,因此 Three.js 庫(以及其他庫)建立在 WebGL 之上,並提供了一個更簡單的 API 來建立常見的 3D 物件、燈光、紋理等。Service Worker API 也非常複雜,因此程式碼庫開始出現,使常見的 Service Worker 使用案例更容易實現(請參見 Service Worker Cookbook,瞭解幾個有用的程式碼示例)。
- 效果庫:這些庫旨在讓您輕鬆地將特殊效果新增到您的網站。這在 “DHTML” 還是流行的流行語時更有用,實現效果需要大量複雜的 JavaScript,但現在瀏覽器內建了許多 CSS 功能和 API,可以更輕鬆地實現效果。
- UI 庫:提供實現複雜 UI 功能的方法,否則這些功能將很難實現和跨瀏覽器工作,例如 Foundation、Bootstrap 和 Material-UI(後者是一組與 React 框架一起使用的元件)。這些往往被用作整個網站佈局的基礎;將它們僅用於一個 UI 功能通常很困難。
- 規範化庫:為您提供簡單的語法,讓您輕鬆完成任務,而不必擔心跨瀏覽器差異。庫將在後臺操作適當的 API,因此該功能將在任何瀏覽器中都能正常工作(理論上)。例如,LocalForage 是一個用於客戶端資料儲存的庫,它提供了一個簡單的語法來儲存和檢索資料。在後臺,它使用瀏覽器可用的最佳 API 來儲存資料,無論是 IndexedDB、Web Storage,甚至 Web SQL(現已棄用,但在安全上下文中仍受 Chromium 瀏覽器支援)。另一個例子是 jQuery
在選擇要使用的庫時,請確保它在您想要支援的瀏覽器集中都能正常工作,並徹底測試您的實現。還要確保該庫很流行並且得到良好支援,並且不太可能在下週就過時。與其他開發人員交談,瞭解他們推薦什麼,看看該庫在 GitHub(或其他任何儲存它的位置)上的活動和貢獻者數量等等。
庫的基本使用通常包括下載庫的檔案(JavaScript,可能還有一些 CSS 或其他依賴項),並將它們附加到您的頁面(例如,透過 <script> 元素),雖然這些庫通常還有許多其他使用選項,例如將它們安裝為 Bower 元件,或透過 Webpack 模組捆綁器將它們作為依賴項包含在內。您需要閱讀庫的各個安裝頁面以瞭解更多資訊。
注意: 您在網上瀏覽時還會遇到 JavaScript 框架,例如 Ember 和 Angular。 庫通常用於解決單個問題並將其引入現有網站,而框架則更傾向於為開發複雜的 Web 應用程式提供完整的解決方案。
填充
填充也包含您可以放入專案中的第三方 JavaScript 檔案,但它們與庫不同——庫傾向於增強現有功能並使其更輕鬆,而填充則提供根本不存在的功能。 填充使用 JavaScript 或其他技術完全構建對瀏覽器不支援的功能的支援。 例如,您可以使用像 es6-promise 這樣的填充使 promise 在瀏覽器中工作,而這些瀏覽器本身並不支援 promise。
讓我們完成一個練習——在這個僅用於演示目的的示例中,我們使用了一個 Fetch 填充和一個 es6-promise 填充。 雖然 Fetch 和 promise 在現代瀏覽器中完全支援,但如果我們針對不支援 Fetch 的瀏覽器,該瀏覽器很可能也不支援 Fetch,並且 Fetch 嚴重依賴 promise
- 首先,在新的目錄中製作我們 fetch-polyfill.html 示例和 我們漂亮的鮮花影像 的本地副本。 我們將編寫程式碼來獲取鮮花影像並將其顯示在頁面中。
- 接下來,在與 HTML 相同的目錄中儲存 Fetch 填充 的副本。
- 使用以下程式碼將填充指令碼應用於頁面——將它們放在現有的
<script>元素上方,這樣當我們開始嘗試使用 Fetch 時,它們已經在頁面上可用(我們也從 CDN 載入 Promise 填充,因為 IE11 支援 promise,而 fetch 需要 promise)html<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script> <script src="fetch.js"></script> - 在原始的
<script>中,新增以下程式碼jsconst myImage = document.querySelector(".my-image"); fetch("flowers.jpg").then((response) => { response.blob().then((myBlob) => { const objectURL = URL.createObjectURL(myBlob); myImage.src = objectURL; }); }); - 如果您在不支援 Fetch 的瀏覽器中載入它,您仍然應該看到鮮花影像出現——很酷!

注意: 您可以在 fetch-polyfill-finished.html 中找到我們完成的版本(另請參見 原始碼)。
注意: 同樣,您會遇到許多不同的方法來使用不同的填充——請參閱每個填充的單獨文件。
您可能在想的一件事是“為什麼我們應該始終載入填充程式碼,即使我們不需要它?” 這是一個很好的觀點——隨著您的網站變得越來越複雜,並且您開始使用更多庫、填充等,您可能會開始載入大量額外的程式碼,這可能會開始影響效能,尤其是在功能較弱的裝置上。 只有在需要時載入檔案是有意義的。
執行此操作需要在您的 JavaScript 中進行一些額外的設定。 您需要某種功能檢測測試來檢測瀏覽器是否支援我們嘗試使用的功能
if (browserSupportsAllFeatures()) {
main();
} else {
loadScript("polyfills.js", main);
}
function main(err) {
// actual app code goes in here
}
因此,我們首先執行一個條件語句,該語句檢查函式 browserSupportsAllFeatures() 是否返回 true。 如果是,我們執行 main() 函式,該函式將包含我們應用程式的所有程式碼。 browserSupportsAllFeatures() 看起來像這樣
function browserSupportsAllFeatures() {
return window.Promise && window.fetch;
}
在這裡,我們測試了 Promise 物件和 fetch() 函式是否存在於瀏覽器中。 如果兩者都存在,則該函式返回 true。 如果該函式返回 false,則我們執行條件語句第二部分中的程式碼——這將執行一個名為 loadScript() 的函式,該函式將填充載入到頁面中,然後在載入完成後執行 main()。 loadScript() 看起來像這樣
function loadScript(src, done) {
const js = document.createElement("script");
js.src = src;
js.onload = () => {
done();
};
js.onerror = () => {
done(new Error(`Failed to load script ${src}`));
};
document.head.appendChild(js);
}
該函式建立一個新的 <script> 元素,然後將其 src 屬性設定為我們作為第一個引數指定的路徑(在上面的程式碼中呼叫時為 'polyfills.js')。 載入完成後,我們執行我們作為第二個引數指定的函式(main())。 如果在載入指令碼時發生錯誤,我們仍然呼叫該函式,但使用我們可以檢索的自定義錯誤,以幫助除錯可能發生的任何問題。
請注意,polyfills.js 基本上是我們使用的兩個填充放在一起組成一個檔案。 我們手動執行了此操作,但有一些更巧妙的解決方案可以自動為您生成捆綁包——請參閱 Browserify(請參閱 Browserify 入門 獲取基本教程)。 將 JS 檔案捆綁到一個檔案中的做法是個好主意——減少需要發出的 HTTP 請求數量可以提高網站的效能。
您可以在 fetch-polyfill-only-when-needed.html 中看到此程式碼的實際應用(另請參見 原始碼)。 我們想明確一點,我們不能為這段程式碼認領功勞——它最初是由 Philip Walton 編寫的。 檢視他的文章 僅在需要時載入填充 獲取原始程式碼,以及圍繞更廣泛主題的大量有用說明。
JavaScript 轉譯
對於希望立即使用現代 JavaScript 功能的人來說,另一個越來越流行的選擇是將使用最近 ECMAScript 功能的程式碼轉換為在舊版瀏覽器中工作的版本。
注意: 這稱為“轉譯”——您不會將程式碼編譯成較低級別以在計算機上執行(如您對 C 程式碼所說); 相反,您將其更改為以類似抽象級別存在的語法,以便它可以以相同的方式使用,但在略微不同的情況下(在本例中,將 JavaScript 的一種風格轉換為另一種風格)。
一個常見的轉譯器是 Babel.js,但還有其他一些。
不要進行瀏覽器嗅探
從歷史上看,開發人員使用瀏覽器嗅探程式碼來檢測使用者使用的是哪個瀏覽器,併為他們提供適用於該瀏覽器的相應程式碼。
所有瀏覽器都有一個使用者代理字串,用於標識瀏覽器是什麼(版本、名稱、作業系統等)。 許多開發人員實現了不良的瀏覽器嗅探程式碼,並且沒有維護它。 這導致支援的瀏覽器被鎖定在無法使用它們可以輕鬆渲染的網站。 這種情況變得如此普遍,以至於瀏覽器開始在其使用者代理字串中撒謊,聲稱它們是其他瀏覽器(或聲稱它們是所有瀏覽器),以繞過嗅探程式碼。 瀏覽器還實現了設施,允許使用者更改瀏覽器在使用 JavaScript 查詢時報告的使用者代理字串。 所有這些都使瀏覽器嗅探更加容易出錯,而且最終毫無意義。
瀏覽器使用者代理字串的歷史 由 Aaron Andersen 提供了關於瀏覽器嗅探歷史的實用且有趣的內容。 使用 功能檢測(以及 CSS @supports 用於 CSS 功能檢測)可靠地檢測功能是否受支援。 但這樣做,您無需在出現新瀏覽器版本時更改程式碼。
處理 JavaScript 字首
在上一篇文章中,我們討論了大量關於 處理 CSS 字首 的內容。 嗯,新的 JavaScript 實現也曾經使用字首,JavaScript 使用 駱駝式大小寫 而不是 連字元(如 CSS)。 例如,如果在新的 jshint API 物件 Object 上使用字首
- Mozilla 將使用
mozObject - Chrome/Opera/Safari 將使用
webkitObject - Microsoft 將使用
msObject
以下示例使用 Web Audio API
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
在 Web Audio API 的情況下,使用 API 的關鍵入口點在 Chrome/Opera 中透過 webkit 字首版本支援(它們現在支援無字首版本)。 解決這種情況的簡單方法是建立一個新的物件版本,這些物件在某些瀏覽器中帶有字首,並將它們設定為等於無字首版本或字首版本(或需要考慮的任何其他字首版本)——當前檢視網站的瀏覽器支援的任何一個版本都將被使用。
然後,我們使用該物件來操作 API,而不是原始物件。 在這種情況下,我們正在建立一個修改後的 AudioContext 建構函式,然後建立一個新的音訊上下文例項以用於我們的 Web Audio 編碼。
此模式幾乎可以應用於任何帶有字首的 JavaScript 功能。 JavaScript 庫/填充也使用這種程式碼,儘可能地將瀏覽器差異抽象化,使開發人員遠離這些差異。
同樣,帶字首的功能不應在生產網站中使用——它們可能會在沒有任何警告的情況下更改或刪除,並會導致跨瀏覽器問題。 如果您堅持使用帶字首的功能,請確保使用正確的功能。 您可以在 MDN 參考頁面和 caniuse.com 等網站上查詢哪些瀏覽器需要哪些 JavaScript/API 功能的字首。 如果您不確定,您也可以透過在瀏覽器中直接進行一些測試來找出答案。
例如,嘗試進入瀏覽器的開發者控制檯並開始鍵入
window.AudioContext;
如果您的瀏覽器支援此功能,它將自動完成。
尋求幫助
您在使用 JavaScript 時會遇到許多其他問題; 最重要的是真正瞭解如何在網上找到答案。 請參閱 HTML 和 CSS 文章的 查詢幫助部分 獲取我們最好的建議。