JS XMLHttpRequest入門教學(非常詳細)

2020-07-16 10:05:10
在 JavaScript 中,XMLHttpRequest 是用戶端的一個 API,它為瀏覽器與伺服器通訊提供了一個便捷通道。現代瀏覽器都支援 XMLHttpRequest API,如 IE 7+、Firefox、Chrome、Safari 和 Opera。

建立 XMLHttpRequest 物件

XMLHttpRequest 用於在後台與伺服器交換資料。建立 XMLHttpRequest 物件的方法如下:

var xhr = new XMLHttpRequest ();


IE 5.0 版本開始以 ActiveX 元件形式支援 XMLHttpRequest,IE 7.0 版本開始標準化 XMLHttpRequest。不過所有瀏覽器實現的 XMLHttpRequest 物件都提供相同的介面和用法。

範例

下面範例使用工廠模式把定義 XMLHttpRequest 物件進行封裝,這樣只需要呼叫 creatXHR() 方法就可以返回一個 XMLHttpRequest 物件。
//建立XMLHttpRequest 物件
//引數:無
//返回值:XMLHttpRequest 物件
function createXHR () {
    var XHR = [  //相容不同瀏覽器和版本得建立函數陣列
        function () { return new XMLHttpRequest () },
        function () { return new ActiveXObject ("Msxml2.XMLHTTP") },
        function () { return new ActiveXObject ("Msxml3.XMLHTTP") },
        function () { return new ActiveXObject ("Microsoft.XMLHTTP") }
    ];
    var xhr = null;
    //嘗試呼叫函數,如果成功則返回XMLHttpRequest物件,否則繼續嘗試
    for (var i = 0; i < XHR.length; i ++) {
        try {
            xhr = XHR[i]();
        } catch(e) {
            continue  //如果發生異常,則繼續下一個函數呼叫
        }
        break;  //如果成功,則中止迴圈
    }
    return xhr;  //返回物件範例
}
在上面程式碼中,首先定義一個陣列,收集各種建立 XMLHttpRequest 物件的函數。第 1 個函數是標準用法,其他函數主要針對 IE 瀏覽器的不同版本嘗試建立 ActiveX 物件。然後設定變數 xhr 為 null,表示為空物件。接著遍歷工廠內所有函數並嘗試執行它們,為了避免發生異常,把所有呼叫函數放在 try 中執行,如果發生錯誤,則在 catch 中捕獲異常並執行 continue 命令,返回繼續執行,避免丟擲異常。如果建立成功,則中止迴圈,返回 XMLHttpRequest 物件。

建立連線

在 JavaScript 中,使用 XMLHttpRequest 物件的 open() 方法可以建立一個 HTTP 請求。用法如下:

xhr.open(method, url, async, username, password);

其中 xhr 表示 XMLHttpRequest 物件,open() 方法包含 5 個引數,說明如下:
  • method:HTTP 請求方法,必須引數,值包括 POST、GET 和 HEAD,大小寫不敏感。
  • url:請求的 URL 字串,必須引數,大部分瀏覽器僅支援同源請求。
  • async:指定請求是否為非同步方式,預設為 true。如果為 false,當狀態改變時會立即呼叫 onreadystatechange 屬性指定的回撥函數。
  • username:可選引數,如果伺服器需要驗證,該引數指定使用者名稱,如果未指定,當伺服器需要驗證時,會彈出驗證視窗。
  • password:可選引數,驗證資訊中的密碼部分,如果使用者名稱為空,則該值將被忽略。

建立連線後,可以使用 send() 方法傳送請求。用法如下:

xhr.send(body);

引數 body 表示將通過該請求傳送的資料,如果不傳遞資訊,可以設定為 null 或者省略。

傳送請求後,可以使用 XMLHttpRequest 物件的 responseBody、responseStream、responseText 或 responseXML 屬等待接收響應資料。

範例

下面範例簡單演示了如何實現非同步通訊的方法。
var xhr = creatXHR();  //範例化XMLHttpRequest 物件
xhr.open ("GET", "server.txt", false");  //建立連線
xhr.send(null);  //傳送請求
console.log(xhr.responseText);  //接收資料
在伺服器端(server.txt)中輸入下面的字串。
Hello World  //伺服器端指令碼
在瀏覽器控制台會顯示“Hello World”的提示資訊。該字串是從伺服器端響應的字串。

傳送 GET 請求

在 JavaScript 中,傳送 GET 請求簡單、方便,適用於簡單字串,不適用於大容量或加密資料。實現方法:將包含查詢字串的 URL 傳入 open() 方法,設定第 1 個引數值為 GET 即可。伺服器能夠通過查詢字串接收使用者資訊。

範例

下面範例以 GET 方式向伺服器傳遞一條資訊 callback=functionName。
<input name="submit" type="button" id="submit" value="向伺服器發出請求" />
<script>
    window.onload = function () {  //頁面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var url = "server.php?callback=functionName";  //設定查詢字串
            var xhr = createXHR();  //範例化XMLHttpRequest 物件
            xhr.open("GET", url, false);  //建立連線,要求同步響應
            xhr.send(null);  //傳送請求
            console.log(xhr.responseText);  //接收資料
        }
    }
</script>
在伺服器端檔案(server.php)中輸入下面的程式碼,獲取查詢字串中 callback 的引數值,並把該值響應給用戶端。
<?php
    echo $_GET["callback"];
?>
在瀏覽器中預覽頁面,當單擊提交按鈕時,在控制台顯示傳遞的引數值。

查詢字串通過問號?作為字首附加在 URL 的末尾,傳送資料是以連字元&連線的一個或多個名值對。

傳送 POST 請求

在 JavaScript 中,POST 請求允許傳送任意型別、長度的資料,多用於表單提交,以 send() 方法進行傳遞,而不以查詢字串的方式進行傳遞。POST 字串與 GET 字串的格式相同。格式如下:

send("name1=value1&name2=value2...");

範例

使用 POST 方法向伺服器傳遞資料。
window.onload = function () {  //頁面初始化
    var b = document.getElementsByTagName("input")[0];
    b.onclick = function () {
        var url = "server.php";  //設定請求的地址
        var xhr = createXHR();  //範例化XMLHttpRequest 物件
        xhr.open("POST", url, false);  //建立連線,要求同步響應
        xhr.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');  //設定為表單方式提交
        xhr.send("callback=functionName");  //傳送請求
        console.log(xhr.responseText);  //接收資料
    }
}
在 open() 方法中,設定第一個引數為 POST,然後使用 setRequestHeader() 方法設定請求訊息的內容型別為“application/x-www-form-urlencoded”,它表示傳遞的是表單值,一般使用 POST 傳送請求時都必須設定該選項,否則伺服器無法識別傳遞過來的資料。

在伺服器端設計接收 POST 方式傳遞的資料,並進行響應。
<?php
    echo $_POST["callback"];
?>

序列格式化資料

GET 和 POST 方法都是以名值對的字串格式傳送資料的。

物件資訊

下面是一個包含 3 個名值對的 JSON 型別資料。
{ user : "css8", pass : "123456", email : "[email protected]" }
將 JSON 資料轉換為序列格式化顯示如下。
'user="css8" & pass="123456" & email="[email protected]"'

陣列資訊

下面是一組有序的 JSON 資訊,包含多個值。
[{name : "user", value : "css8"} , {name : "pass", value : "123456"), {name : "email", value : "[email protected]"}]
將上面資料轉換為序列格式顯示如下。
'user="css8" & pass="123456" & email="[email protected]"'

【範例】為了方便開發,下面定義一個工具函數,該函數能夠把資料轉換為序列格式化字串並返回。
//把JSON資料轉換為序列字串
//引數:data表示陣列或物件型別的資料
//返回值:序列字串
function JSONtoString (data) {
    var a = [];  //臨時陣列
    if (data.constructor == Array) {  //處理陣列
        for (var i = 0; i < data.length; i ++) {
            a.push(data[i].name + "=" + encodeURIComponent(data[i].value));
        }
    } else {  //處理物件
        for (var i in data) {
            a.push(i + "=" + encodeURIComponent(data[i]));
        }
    }
    return a.join("&");  //把陣列轉換為序列字串,並返回
}

非同步響應狀態

在 JavaScript 中,使用 readyState 屬性可以實時跟蹤非同步響應狀態。當該屬性值發生變化時,會觸發 readystatechange 事件,呼叫系結的回撥函數。readyState 屬性值說明如表所示。

readyState屬性值
返回值 說明
0 未初始化。表示物件已經建立,但是尚未初始化,尚未呼叫 open() 方法
1 初始化。表示物件已經建立,尚未呼叫 send() 方法
2 傳送資料。表示 send() 方法已經呼叫,但是當前的狀態及 HTTP 頭未知
3 資料傳送中。已經接收部分資料,因為響應及 HTTP 頭不安全,這時通過 responseBody 和 responseText 獲取部分資料會出現錯誤
4 完成。資料接收完畢,此時可以通過 responseBody 和 responseText 獲取完整的響應資料
如果 readyState 屬性值為 4,則說明響應完畢,那麼就可以安全的讀取響應的資料。

考慮到各種特殊情況,更安全的方法是同時監測 HTTP 狀態碼,只有當 HTTP 狀態碼為 200 時,才說明 HTTP 響應順利完成。

範例

下面範例中,修改請求為非同步響應請求,然後通過 status 屬性獲取當前的 HTTP 狀態碼。如果 readyState 屬性值為 4,且 status(狀態碼)屬性值為 200,則說明 HTTP 請求和響應過程順利完成,這時可以安全、非同步的讀取資料。
window.onload = function () {  //頁面初始化
    var b = document.getElementsByTagName("input")[0];
    b.onclick = function () {
        var url = "server.php";  //設定請求的地址
        var xhr = createXHR();  //範例化XMLHttpRequest物件
        xhr.open("POST", url, true);  //建立間接,要求非同步響應
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');  //設定為表單方式提交
        xhr.onreadystatechange = function () {  //係結響應狀態事件監聽函數
            if (xhr.readyState == 4) {  //監聽readyState狀態
                if (xhr.status == 200 || xhr.status == 0) {  //監聽HTTP狀態碼
                    console.log(xhr.responseText);  //接收資料
                }
            }
        }
        xhr.send("callback=functionName");  //傳送請求
    }
}

中止請求

使用 abort() 方法可以中止正在進行的請求。用法如下:

xhr.onreadystatechange = function () {};  //清理事件響應函數
xhr.abort();  //中止請求


在呼叫 abort() 方法前,應先清除 onreadystatechange 事件處理常式,因為 IE 和 Mozilla 在請求中止後也會啟用這個事件處理常式。如果給 onreadystatechange 屬性設定為 null,則 IE 會發生異常,所以為它設定一個空函數。

獲取 XML 資料

XMLHttpRequest 物件通過 responseText、responseBody、responseStream 或 responseXML 屬性獲取響應資訊,說明如下表所示,它們都是唯讀屬性。

XMLHttpRequest 物件響應資訊屬性
響應資訊 說明
responseBody 將響應資訊正文以 Unsigned Byte 陣列形式返回
responseStream  以 ADO Stream 物件的形式返回響應資訊
responseText 將響應資訊作為字串返回
responseXML 將響應資訊格式化為 XML 文件格式返回

在實際應用中,一般將格式設定為 XML、HTML、JSON 或其他純文字格式。具體使用哪種響應格式,可以參考下面幾條原則。
  • 如果向頁面中新增大塊資料,選擇 HTML 格式會比較方便。
  • 如果需要共同作業開發,且專案龐雜,選擇 XML 格式會更通用。
  • 如果要檢索複雜的資料,且結構複雜,那麼選擇 JSON 格式更加輕便。

範例1

在伺服器端建立一個簡單的 XML 文件。
<?xml version="1.0" encoding="utf-8"?>
<the>XML 資料</the>
然後,在用戶端進行如下請求。
<input name="submit" type="button" id="submit" value="向伺服器發出請求" />
<script>
    window.onload = function () {  //頁面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = createXHR();  //範例化XMLHttpRequest物件
            xhr.open("GET", "server.xml", true);  //建立連線,要求非同步響應
            xhr.onreadystatechange = function () {  //係結響應狀態事件監聽函數
                if (xhr.readyState == 4) {  //監聽readyState狀態
                    if (xhr.state == 200 || xhr.status == 0) {  //監聽HTTP狀態碼
                        var info = xhr.responseXML;
                        console.log(info.getElementsByTagName("the")[0].firstChild.data);  //返回元資訊字串“XML 資料”
                    }
                }
            }
            xhr.send();  //傳送請求
        }
    }
</script>
在上面程式碼中,使用 XML DOM 的 getElementsByTagName() 方法獲取 the 節點,然後再定位第一個 the 節點的子節點內容。此時如果繼續使用 responseText 屬性來讀取資料,則會返回 XML 原始碼字串。

範例2

以範例 1 為例,使用伺服器端指令碼生成 XML 結構資料。
<?php
    header('Content-Type: text/html;');
    echo '<?xml version="1.0" encoding="utf-8"?><the>XML 資料</the>';  //輸出XML
?>

獲取 HTML 字串

設計響應資訊為 HTML 字串,然後使用 innerHTML 把獲取的字串插入到網頁中。

範例

在伺服器端設計響應資訊為 HTML 結構程式碼。
<table border="1" width="100%">
    <tr><td>RegExp.exec()</td><td>通用的匹配模式</td></tr>
    <tr><td>RegExp.test()</td><td>檢測一個字串是否匹配某個模式</td></tr>
</table>
然後在用戶端可以這樣接收響應資訊。
<input name="submit" type="button" id="submit" value="向伺服器發出請求" />
<script>
    window.onload = function () {  //頁面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = createXHR();  //範例化XMLHttpRequest物件
            xhr.open("GET", "server.xml", true);  //建立連線,要求非同步響應
            xhr.onreadystatechange = function () {  //係結響應狀態事件監聽函數
                if (xhr.readyState == 4) {  //監聽readyState狀態
                    if (xhr.state == 200 || xhr.status == 0) {  //監聽HTTP狀態碼
                        var o = document.getElementById("grid");
                        o.innerHTML = xhr.responseText;  //直接插入到頁面中
                    }
                }
            }
            xhr.send();  //傳送請求
        }
    }
</script>

在某些情況下,HTML 字串可能為用戶端解析響應資訊節省了一些 JavaScript 指令碼,但是也帶來了一些問題。
  • 響應資訊中包含大量無用的字元,響應資料會變得很臃腫。因為 HTML 標記不含有資訊,完全可以把它們放置在用戶端,由 JavaScript 指令碼負責生成。
  • 響應資訊中包含的 HTML 結構無法有效利用,對於 JavaScript 指令碼來說,它們僅僅是一堆字串。同時結構和資訊混合在一起,也不符合標準化設計原則。

獲取 JavaScript 指令碼

設計相應為 JavaScript 程式碼,與 JSON 資料不同,它是可執行的命令或指令碼。

範例

在伺服器端請求檔案中包含下面一個函數。
function () {
    var d = new Date();
    return d.toString();
}
然後在用戶端執行下面的請求。
<input name="submit" type="button" id="submit" value="向伺服器發出請求" />
<script>
    window.onload = function () {  //頁面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = createXHR();  //範例化XMLHttpRequest物件
            xhr.open("GET", "server.xml", true);  //建立連線,要求非同步響應
            xhr.onreadystatechange = function () {  //係結響應狀態事件監聽函數
                if (xhr.readyState == 4) {  //監聽readyState狀態
                    if (xhr.state == 200 || xhr.status == 0) {  //監聽HTTP狀態碼
                        var info = xhr.responseText;
                        var o = eval("(" + info + ")" + "()");  //用eval()把字串轉換為指令碼
                        console.log(o);  //返回用戶端當前資訊
                    }
                }
            }
            xhr.send();  //傳送請求
        }
    }
</script>
使用 eval() 方法時,在字串前後附加兩個小括號:一個是包含函數結構體的,一個是表示呼叫函數的。不建議直接使用 JavaScript 程式碼作為響應格式,因為它不能傳遞更豐富的資訊,同時 JavaScript 指令碼極易引發安全隱患。

獲取 JSON 資料

使用 responseText 可以獲取 JSON 格式的字串,然後使用 eval() 方法將其解析為本地 JavaScript 指令碼,再從該資料物件中讀取資訊。

範例

在伺服器端請求檔案中包含下面 JSON 資料。
{ user : "css8", pass : "123456", email : "[email protected]" }
然後在用戶端執行下面的請求。把返回 JSON 字串轉換為物件,然後讀取屬性值。
<input name="submit" type="button" id="submit" value="向伺服器發出請求" />
<script>
    window.onload = function () {  //頁面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = createXHR();  //範例化XMLHttpRequest物件
            xhr.open("GET", "server.xml", true);  //建立連線,要求非同步響應
            xhr.onreadystatechange = function () {  //係結響應狀態事件監聽函數
                if (xhr.readyState == 4) {  //監聽readyState狀態
                    if (xhr.state == 200 || xhr.status == 0) {  //監聽HTTP狀態碼
                        var info = xhr.responseText;
                        var o = eval("(" + info + ")");  //呼叫eval()把字串轉換為本地指令碼
                        console.log(info);  //顯示JSON物件字串
                        console.log(o.user);  //讀取物件屬性值,返回字串“css8”
                    }
                }
            }
            xhr.send();  //傳送請求
        }
    }
</script>
eval() 方法在解析 JSON 字串時存在安全隱患。如果 JSON 字串中包含惡意程式碼,在呼叫回撥函數時可能會被執行。解決方法:先對 JSON 字串進行過濾,遮蔽掉敏感或惡意程式碼。不過,確信所響應的 JSON 字串是安全的,沒有被人惡意攻擊,那麼可以使用 eval() 方法解析 JSON 字串。

獲取純文字

對於簡短的資訊,可以使用純文字格式進行響應。但是純文字資訊在傳遞過程中容易丟失,且沒有辦法檢測資訊的完整性。

範例

伺服器端響應資訊為字串“true”,則可以在用戶端這樣設計。
var xhr = createXHR();  //範例化XMLHttpRequest物件
xhr.open("GET", "server.txt", true);  //建立連線,要求非同步響應
xhr.nreadystatechange = function () {  //係結響應狀態事件監聽函數
    if (xhr.readyState == 4) {  //監聽readyState函數
        if (xhr.status == 200 || xhr.status == 0) {  //監聽HTTP狀態碼
            var info = xhr.responseText;
            if (info == "true") console.log("文字資訊傳輸完整");  //檢測資訊是否完整
            else console.log("文字資訊可能存在丟失");
        }
    }
}
xhr.send();  //傳送請求

獲取和設定頭部訊息

HTTP 請求和響應都包含一組頭部訊息,獲取和設定頭部訊息可以使用下面兩個方法。
  • getAllResponseHeaders():獲取響應的 HTTP頭部訊息。
  • getResponseHeader("Header-name"):獲取指定的 HTTP 頭部訊息。

範例

下面範例將獲取 HTTP 響應的所有頭部訊息。
var xhr = createXHR();
var url = "server.txt";
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && xhr.status == 200) {
        console.log(xhr.getAllResponseHeaders());
    }
}
xhr.send(null);
如果要獲取指定的某個頭部訊息,可以使用 getResponseHeader() 方法,引數為獲取頭部的名稱。例如,獲取 Content-Type 頭部的值,可以這樣設計。
console.log(xhr.getResponseHeader("Content-Type"));
除了可以獲取這些頭部訊息外,還可以使用 setResponseHeader() 方法在傳送請求中設定各種頭部訊息。用法如下:
xhr.setResponseHeader("Header-name", "value");
其中 Header-name 表示頭部訊息的名稱,value 表示訊息的具體值。例如,使用 POST 方法傳遞表單資料,可以設定如下頭部訊息。
xhr.setResponseHeader("Content-Type", "application/x-www-form-urlencoded");

本節講解了 XMLHttpRequest 入門教學及 XMLHttpRequest 1.0 版本,下節講解 XMLHttpRequest 2.0 版本新特性。