使用 XMLHttpRequest

Baseline 廣泛可用 *

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2015 年 7 月⁩以來,各瀏覽器均已提供此特性。

* 此特性的某些部分可能存在不同級別的支援。

在本指南中,我們將探討如何使用 XMLHttpRequest 發出 HTTP 請求,以便在網站和伺服器之間交換資料。

其中包含 XMLHttpRequest 的常見和不太常見的用例示例。

傳送 HTTP 請求

  1. 建立 XMLHttpRequest 物件
  2. 開啟一個 URL
  3. 傳送請求。

交易完成後,XMLHttpRequest 物件將包含有用的資訊,例如響應正文和結果的 HTTP 狀態

js
function reqListener() {
  console.log(this.responseText);
}

const req = new XMLHttpRequest();
req.addEventListener("load", reqListener);
req.open("GET", "http://www.example.org/example.txt");
req.send();

請求型別

透過 XMLHttpRequest 發出的請求可以透過兩種方式獲取資料:非同步或同步。請求的型別由 XMLHttpRequest.open() 方法上的可選 async 引數(第三個引數)決定。如果此引數為 true 或未指定,則 XMLHttpRequest 將非同步處理;否則,將同步處理。有關這兩種請求型別的詳細討論和演示,請參閱 同步和非同步請求 頁面。您不能在 Web Worker 之外使用同步請求,因為它會凍結主介面。

注意: 建構函式 XMLHttpRequest 不僅限於 XML 文件。它以 "XML" 開頭,因為在建立它時,最初用於非同步資料交換的主要格式是 XML。

處理響應

XMLHttpRequest() 建構函式定義了幾種 響應屬性。這些屬性告訴發出 XMLHttpRequest 的客戶端有關響應狀態的重要資訊。以下部分概述了一些處理非文字響應型別時可能涉及的操作和分析的場景。

分析和操作 responseXML 屬性

如果您使用 XMLHttpRequest 獲取遠端 XML 文件的內容,則 responseXML 屬性將是一個包含已解析 XML 文件的 DOM 物件。這可能會難以操作和分析。有四種主要方法可以分析此 XML 文件:

  1. 使用 XPath 來定位(或指向)其中的部分。
  2. 手動 解析和序列化 XML 為字串或物件。
  3. 使用 XMLSerializer 將 **DOM 樹序列化為字串或檔案**。
  4. 如果您始終事先知道 XML 文件的內容,則可以使用 RegExp。如果您使用 RegExp 來掃描換行符,您可能希望刪除換行符。但是,這種方法是“最後的手段”,因為如果 XML 程式碼稍有更改,該方法很可能會失敗。

注意: XMLHttpRequest 現在可以使用 responseXML 屬性為您解釋 HTML。請閱讀有關 XMLHttpRequest 中的 HTML 的文章,瞭解如何執行此操作。

處理包含 HTML 文件的 responseText 屬性

如果您使用 XMLHttpRequest 獲取遠端 HTML 網頁的內容,則 responseText 屬性是一個包含原始 HTML 的字串。這可能會難以操作和分析。有三種主要方法可以分析和解析此原始 HTML 字串:

  1. 使用 XMLHttpRequest.responseXML 屬性,如 XMLHttpRequest 中的 HTML 文章中所述。
  2. 透過 fragment.body.innerHTML 將內容注入 文件片段 的 body 中,並遍歷片段的 DOM。
  3. 如果您始終事先知道 HTML responseText 的內容,則可以使用 RegExp。如果您使用 RegExp 來掃描換行符,您可能希望刪除換行符。但是,這種方法是“最後的手段”,因為如果 HTML 程式碼稍有更改,該方法很可能會失敗。

處理二進位制資料

儘管 XMLHttpRequest 最常用於傳送和接收文字資料,但它也可以用於傳送和接收二進位制內容。有幾種經過充分測試的方法可以將 XMLHttpRequest 的響應強制轉換為傳送二進位制資料。這些方法包括利用 XMLHttpRequest 物件上的 overrideMimeType() 方法,這是一種可行的解決方案。

js
const req = new XMLHttpRequest();
req.open("GET", url);
// retrieve data unprocessed as a binary string
req.overrideMimeType("text/plain; charset=x-user-defined");
/* … */

但是,現在有更現代的技術,因為 responseType 屬性現在支援多種額外的內容型別,這使得傳送和接收二進位制資料更加容易。

例如,考慮這段程式碼片段,它使用 responseType"arraybuffer" 來將遠端內容獲取到 ArrayBuffer 物件中,該物件儲存原始二進位制資料。

js
const req = new XMLHttpRequest();

req.onload = (e) => {
  const arraybuffer = req.response; // not responseText
  /* … */
};
req.open("GET", url);
req.responseType = "arraybuffer";
req.send();

有關更多示例,請參閱 傳送和接收二進位制資料 頁面。

監控進度

XMLHttpRequest 提供了在請求處理過程中偵聽各種事件的能力。這包括定期的進度通知、錯誤通知等。

XMLHttpRequest 傳輸的 DOM progress 事件監控的支援遵循 進度事件規範:這些事件實現了 ProgressEvent 介面。您可以監控以確定正在進行的傳輸狀態的實際事件是:

progress

已檢索資料量已更改。

load

傳輸已完成;所有資料現在都在 response 中。

js
const req = new XMLHttpRequest();

req.addEventListener("progress", updateProgress);
req.addEventListener("load", transferComplete);
req.addEventListener("error", transferFailed);
req.addEventListener("abort", transferCanceled);

req.open();

// …

// progress on transfers from the server to the client (downloads)
function updateProgress(event) {
  if (event.lengthComputable) {
    const percentComplete = (event.loaded / event.total) * 100;
    // …
  } else {
    // Unable to compute progress information since the total size is unknown
  }
}

function transferComplete(evt) {
  console.log("The transfer is complete.");
}

function transferFailed(evt) {
  console.log("An error occurred while transferring the file.");
}

function transferCanceled(evt) {
  console.log("The transfer has been canceled by the user.");
}

我們新增事件監聽器來處理使用 XMLHttpRequest 進行資料傳輸時傳送的各種事件。

注意: 您需要在對請求呼叫 open() 之前新增事件監聽器。否則,progress 事件將不會觸發。

進度事件處理程式(在此示例中由 updateProgress() 函式指定)接收要傳輸的總位元組數以及事件的 totalloaded 欄位中已傳輸的位元組數。但是,如果 lengthComputable 欄位為 false,則總長度未知且將為零。

下載和上傳傳輸都存在進度事件。下載事件在 XMLHttpRequest 物件本身上觸發,如上面的示例所示。上傳事件在 XMLHttpRequest.upload 物件上觸發,如下所示:

js
const req = new XMLHttpRequest();

req.upload.addEventListener("progress", updateProgress);
req.upload.addEventListener("load", transferComplete);
req.upload.addEventListener("error", transferFailed);
req.upload.addEventListener("abort", transferCanceled);

req.open();

注意: file: 協議不支援進度事件。

對於收到的每個資料塊,都會觸發進度事件,包括在最後一個數據包已接收且連線在進度事件觸發之前關閉的情況下的最後一個數據塊。在這種情況下,當該資料包的 load 事件發生時,進度事件會自動觸發。這樣,您就可以只通過監視“progress”事件來可靠地監控進度。

還可以使用 loadend 事件檢測所有三個載入結束條件(abortloaderror)。

js
req.addEventListener("loadend", loadEnd);

function loadEnd(e) {
  console.log(
    "The transfer finished (although we don't know if it succeeded or not).",
  );
}

請注意,根據 loadend 事件接收到的資訊,無法確定是哪種條件導致操作終止;但是,您可以使用此事件來處理需要在所有傳輸結束場景中執行的任務。

獲取最後修改日期

js
function getHeaderTime() {
  console.log(this.getResponseHeader("Last-Modified")); // A valid GMTString date or null
}

const req = new XMLHttpRequest();
req.open(
  "HEAD", // use HEAD when you only need the headers
  "your-page.html",
);
req.onload = getHeaderTime;
req.send();

當最後修改日期更改時執行某些操作

讓我們建立兩個函式。

js
function getHeaderTime() {
  const lastVisit = parseFloat(
    window.localStorage.getItem(`lm_${this.filepath}`),
  );
  const lastModified = Date.parse(this.getResponseHeader("Last-Modified"));

  if (isNaN(lastVisit) || lastModified > lastVisit) {
    window.localStorage.setItem(`lm_${this.filepath}`, Date.now());
    isFinite(lastVisit) && this.callback(lastModified, lastVisit);
  }
}

function ifHasChanged(URL, callback) {
  const req = new XMLHttpRequest();
  req.open("HEAD" /* use HEAD - we only need the headers! */, URL);
  req.callback = callback;
  req.filepath = URL;
  req.onload = getHeaderTime;
  req.send();
}

並進行測試。

js
// Let's test the file "your-page.html"
ifHasChanged("your-page.html", function (modified, visit) {
  console.log(
    `The page '${this.filepath}' has been changed on ${new Date(
      modified,
    ).toLocaleString()}!`,
  );
});

如果您想知道當前頁面是否已更改,請參閱有關 document.lastModified 的文章。

跨站 XMLHttpRequest

現代瀏覽器透過實現 跨域資源共享 (CORS) 標準來支援跨站請求。只要伺服器配置為允許來自您的 Web 應用程式源的請求,XMLHttpRequest 就可以正常工作。否則,將丟擲 INVALID_ACCESS_ERR 異常。

繞過快取

一種跨瀏覽器相容的繞過快取的方法是在 URL 後面附加一個時間戳,確保根據需要包含 "?" 或 "&"。例如:

http://example.com/bar.html -> http://example.com/bar.html?12345
http://example.com/bar.html?foobar=baz -> http://example.com/bar.html?foobar=baz&12345

由於本地快取是按 URL 索引的,這會導致每個請求都唯一,從而繞過快取。

您可以使用以下程式碼自動調整 URL:

js
const req = new XMLHttpRequest();

req.open("GET", url + (/\?/.test(url) ? "&" : "?") + new Date().getTime());
req.send(null);

安全

啟用跨站指令碼的推薦方法是在 XMLHttpRequest 的響應中使用 Access-Control-Allow-Origin HTTP 標頭。

XMLHttpRequest 的停止

如果您最終使用一個 XMLHttpRequest 接收到 status=0statusText=null,這意味著該請求不允許執行。它處於 UNSENT 狀態。這可能的原因是 XMLHttpRequest 的源(在建立 XMLHttpRequest 時)在後續 open() 時發生了變化。這種情況可能發生在,例如,當一個 XMLHttpRequest 在視窗的 onunload 事件上觸發時,預期的 XMLHttpRequest 在要關閉的視窗仍然存在時建立,最後在視窗失去焦點並且另一個視窗獲得焦點時傳送請求(即,open())。避免此問題的最有效方法是在新視窗的 DOMActivate 事件上設定一個監聽器,當已終止視窗的 unload 事件觸發後,該監聽器將被設定。

規範

規範
XMLHttpRequest
# interface-xmlhttprequest

瀏覽器相容性

另見