十八、Ajax 與 Comet

??2005 年书斜,Jesse James Garrett 發(fā)表了一篇在線文章诬辈,題為“Ajax: A new Approach to Web Applications” 。他在這篇文章里介紹了一種技術(shù)荐吉,用他的話說焙糟,就叫 Ajax,是對 Asynchronous JavaScript + XML 的簡寫样屠。
http://www.adaptivepath.com/ideas/essays/archives/000385.php

??這一技術(shù)能夠像服務(wù)器請求額外的數(shù)據(jù)而無需卸載頁面穿撮,會帶來更好的用戶體驗缺脉。Garrett 還解釋了怎樣使用這一技術(shù)改變自從 Web 誕生以來就一直沿用的“單擊”,“等待”的交互模式悦穿。

??Ajax 技術(shù)的核心是 XMLHttpRequest 對象(簡稱 XHR)攻礼,這是由微軟首先引入的一個特性,其他瀏覽器提供商后來都提供了相同的實現(xiàn)栗柒。

??在 XHR 出現(xiàn)之前礁扮,Ajax 式的通信必須借助一些 hack 手段來實現(xiàn),大多數(shù)是使用隱藏的框架或內(nèi)嵌框架瞬沦。

??XHR 為向服務(wù)器發(fā)送請求和解析服務(wù)器響應(yīng)提供了流暢的接口太伊。能夠以異步方式從服務(wù)器取得更多信息,意味著用戶單擊后蛙埂,可以不必刷新頁面也能取得新數(shù)據(jù)倦畅。
??也就是說,可以使用 XHR 對象取得新數(shù)據(jù)绣的,然后再通過 DOM 將新數(shù)據(jù)插入到頁面中叠赐。另外,雖然名字中包含 XML 的成分屡江,但 Ajax 通信與數(shù)據(jù)格式無關(guān)芭概;這種技術(shù)就是無須刷新頁面即可從服務(wù)器取得數(shù)據(jù),但不一定是 XML 數(shù)據(jù)惩嘉。

??實際上罢洲,Garrett 提到的這種技術(shù)已經(jīng)存在很長時間了。在 Garrett 撰寫那篇文章之前文黎,人們通常將這種技術(shù)叫做遠程腳本(remote scripting)惹苗,而且早在 1998 年就有人采用不同的手段實現(xiàn)了這種瀏覽器與服務(wù)器的通信。再往前推耸峭,JavaScript 需要通過 Java applet 或 Flash 電影等中間層向服務(wù)器發(fā)送請求桩蓉。

??而 XHR 則將瀏覽器原生的通信能力提供給了開發(fā)人員,簡化了實現(xiàn)同樣操作的任務(wù)劳闹。

??在重命名為 Ajax 之后院究,大約是 2005 年底 2006 年初,這種瀏覽器與服務(wù)器的通信技術(shù)可謂紅極一時本涕。人們對 JavaScript 和 Web 的全新認識业汰,催生了很多使用原有特性的新技術(shù)和新模式。

??就目前來說菩颖,熟練使用 XHR 對象已經(jīng)成為所有 Web 開發(fā)人員必須掌握的一種技能样漆。

1、XMLHttpRequest 對象

??IE5 是第一款引入 XHR 對象的瀏覽器位他。在 IE5 中氛濒,XHR 對象是通過 MSXML 庫中的一個 ActiveX 對象實現(xiàn)的产场。因此,在 IE 中可能會遇到三種不同版本的 XHR 對象舞竿,即 MSXML2.XMLHttp京景、MSXML2.XMLHttp.3.0 和 MXSML2.XMLHttp.6.0。

??IE7+骗奖、Firefox确徙、Opera、Chrome 和 Safari 都支持原生的 XHR 對象执桌,在這些瀏覽器中創(chuàng)建 XHR 對象要像下面這樣使用 XMLHttpRequest 構(gòu)造函數(shù)鄙皇。

var xhr = new XMLHttpRequest();

??如果你必須還要支持 IE 的早期版本,那么則可以使用下面這個函數(shù)仰挣。

function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != "undefined"){
        if (typeof arguments.callee.activeXString != "string"){
            var versions = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                i, len;
            for (i=0,len=versions.length; i < len; i++){
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                    break;
                } catch (ex){
                    // 跳過
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error("No XHR object available.");
    }
} 

??這個函數(shù)中新增的代碼首先檢測原生 XHR 對象是否存在伴逸,如果存在則返回它的新實例。如果原生對象不存在膘壶,則檢測 ActiveX 對象错蝴。如果這兩種對象都不存在,就拋出一個錯誤颓芭。
??然后顷锰,就可以使用下面的代碼在所有瀏覽器中創(chuàng)建 XHR 對象了。

var xhr = createXHR();

??由于其他瀏覽器中對 XHR 的實現(xiàn)與 IE 最早的實現(xiàn)是兼容的亡问,因此就可以在所有瀏覽器中都以相同方式使用上面創(chuàng)建的 xhr 對象官紫。

1.1、XHR 的用法

??在使用 XHR 對象時州藕,要調(diào)用的第一個方法是 open()束世,它接受 3 個參數(shù):要發(fā)送的請求的類型("get"、"post"等)床玻、請求的 URL 和表示是否異步發(fā)送請求的布爾值良狈。下面就是調(diào)用這個方法的例子。

xhr.open("get", "example.php", false);

??這行代碼會啟動一個針對 example.php 的 GET 請求笨枯。有關(guān)這行代碼,需要說明兩點:一是 URL 相對于執(zhí)行代碼的當前頁面(當然也可以使用絕對路徑)遇西;二是調(diào)用 open() 方法并不會真正發(fā)送請求馅精,而只是啟動一個請求以備發(fā)送。

??只能向同一個域中使用相同端口和協(xié)議的 URL 發(fā)送請求粱檀。如果 URL 與啟動請求的頁面有任何差別洲敢,都會引發(fā)安全錯誤。

??要發(fā)送特定的請求茄蚯,必須像下面這樣調(diào)用 send() 方法:

xhr.open("get", "example.txt", false);
xhr.send(null);

??這里的 send() 方法接收一個參數(shù)压彭,即要作為請求主體發(fā)送的數(shù)據(jù)睦优。如果不需要通過請求主體發(fā)送數(shù)據(jù),則必須傳入 null壮不,因為這個參數(shù)對有些瀏覽器來說是必需的汗盘。
??調(diào)用 send() 之后,請求就會被分派到服務(wù)器询一。由于這次請求是同步的隐孽,JavaScript 代碼會等到服務(wù)器響應(yīng)之后再繼續(xù)執(zhí)行。在收到響應(yīng)后健蕊,響應(yīng)的數(shù)據(jù)會自動填充 XHR 對象的屬性菱阵,相關(guān)的屬性簡介如下。

  • responseText:作為響應(yīng)主體被返回的文本缩功。
  • responseXML:如果響應(yīng)的內(nèi)容類型是"text/xml"或"application/xml"晴及,這個屬性中將保存包含著響應(yīng)數(shù)據(jù)的 XML DOM 文檔。
  • status:響應(yīng)的 HTTP 狀態(tài)嫡锌。
  • statusText:HTTP 狀態(tài)的說明虑稼。

??在接收到響應(yīng)后,第一步是檢查 status 屬性世舰,以確定響應(yīng)已經(jīng)成功返回动雹。一般來說,可以將 HTTP 狀態(tài)代碼為 200 作為成功的標志跟压。此時胰蝠,responseText 屬性的內(nèi)容已經(jīng)就緒,而且在內(nèi)容類型正確的情況下震蒋,responseXML 也應(yīng)該能夠訪問了茸塞。
??此外,狀態(tài)代碼為 304 表示請求的資源并沒有被修改查剖,可以直接使用瀏覽器中緩存的版本钾虐;當然,也意味著響應(yīng)是有效的笋庄。為確保接收到適當?shù)捻憫?yīng)效扫,應(yīng)該像下面這樣檢查上述這兩種狀態(tài)代碼:

xhr.open("get", "example.txt", false);
xhr.send(null);

if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
    alert(xhr.responseText);
} else {
    alert("Request was unsuccessful: " + xhr.status);
}

??根據(jù)返回的狀態(tài)代碼,這個例子可能會顯示由服務(wù)器返回的內(nèi)容直砂,也可能會顯示一條錯誤消息艾疟。
??我們建議讀者要通過檢測 status 來決定下一步的操作亮航,不要依賴 statusText冻记,因為后者在跨瀏覽器使用時不太可靠晦毙。
??另外,無論內(nèi)容類型是什么,響應(yīng)主體的內(nèi)容都會保存到 responseText 屬性中摹迷;而對于非 XML 數(shù)據(jù)而言疟赊,responseXML 屬性的值將為 null。
??有的瀏覽器會錯誤地報告 204 狀態(tài)代碼峡碉。IE 中 XHR 的 ActiveX 版本會將 204 設(shè)置為 1223近哟,而 IE 中原生的 XHR 則會將 204 規(guī)范化為 200。Opera 會在取得 204 時報告 status 的值為 0异赫。

??像前面這樣發(fā)送同步請求當然沒有問題椅挣,但多數(shù)情況下,我們還是要發(fā)送異步請求塔拳,才能讓 JavaScript 繼續(xù)執(zhí)行而不必等待響應(yīng)鼠证。此時,可以檢測 XHR 對象的 readyState 屬性靠抑,該屬性表示請求/響應(yīng)過程的當前活動階段量九。這個屬性可取的值如下。

  • 0:未初始化颂碧。尚未調(diào)用 open() 方法荠列。
  • 1:啟動。已經(jīng)調(diào)用 open() 方法载城,但尚未調(diào)用 send() 方法肌似。
  • 2:發(fā)送。已經(jīng)調(diào)用 send() 方法诉瓦,但尚未接收到響應(yīng)川队。
  • 3:接收。已經(jīng)接收到部分響應(yīng)數(shù)據(jù)睬澡。
  • 4:完成固额。已經(jīng)接收到全部響應(yīng)數(shù)據(jù),而且已經(jīng)可以在客戶端使用了煞聪。

??只要 readyState 屬性的值由一個值變成另一個值斗躏,都會觸發(fā)一次 readystatechange 事件∥舾可以利用這個事件來檢測每次狀態(tài)變化后 readyState 的值啄糙。
??通常,我們只對 readyState 值為 4 的階段感興趣云稚,因為這時所有數(shù)據(jù)都已經(jīng)就緒迈套。不過,必須在調(diào)用 open() 之前指定 onreadystatechange 事件處理程序才能確奔盍郏跨瀏覽器兼容性。下面來看一個例子踱蛀。

var xhr = createXHR();

xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
     }
};
xhr.open("get", "example.txt", true);
xhr.send(null); 

??以上代碼利用 DOM 0 級方法為 XHR 對象添加了事件處理程序窿给,原因是并非所有瀏覽器都支持 DOM 2 級方法贵白。
??與其他事件處理程序不同,這里沒有向 onreadystatechange 事件處理程序中傳遞 event 對象崩泡;必須通過 XHR 對象本身來確定下一步該怎么做禁荒。

??這個例子在 onreadystatechange 事件處理程序中使用了 xhr 對象,沒有使用 this 對象角撞,原因是 onreadystatechange 事件處理程序的作用域問題呛伴。如果使用 this 對象,在有的瀏覽器中會導致函數(shù)執(zhí)行失敗谒所,或者導致錯誤發(fā)生热康。因此,使用實際的 XHR 對象實例變量是較為可靠的一種方式劣领。

??另外姐军,在接收到響應(yīng)之前還可以調(diào)用 abort() 方法來取消異步請求,如下所示:

xhr.abort();

??調(diào)用這個方法后尖淘,XHR 對象會停止觸發(fā)事件奕锌,而且也不再允許訪問任何與響應(yīng)有關(guān)的對象屬性。在終止請求之后村生,還應(yīng)該對 XHR 對象進行解引用操作惊暴。由于內(nèi)存原因,不建議重用 XHR 對象趁桃。

1.2辽话、HTTP 頭部信息

??每個 HTTP 請求和響應(yīng)都會帶有相應(yīng)的頭部信息,其中有的對開發(fā)人員有用镇辉,有的也沒有什么用屡穗。
??XHR 對象也提供了操作這兩種頭部(即請求頭部和響應(yīng)頭部)信息的方法。

??默認情況下忽肛,在發(fā)送 XHR 請求的同時村砂,還會發(fā)送下列頭部信息。

  • Accept:瀏覽器能夠處理的內(nèi)容類型屹逛。
  • Accept-Charset:瀏覽器能夠顯示的字符集础废。
  • Accept-Encoding:瀏覽器能夠處理的壓縮編碼。
  • Accept-Language:瀏覽器當前設(shè)置的語言罕模。
  • Connection:瀏覽器與服務(wù)器之間連接的類型评腺。
  • Cookie:當前頁面設(shè)置的任何 Cookie。
  • Host:發(fā)出請求的頁面所在的域 淑掌。
  • Referer:發(fā)出請求的頁面的 URI蒿讥。注意,HTTP 規(guī)范將這個頭部字段拼寫錯了,而為保證與規(guī)范一致芋绸,也只能將錯就錯了媒殉。(這個英文單詞的正確拼法應(yīng)該是 referrer。)
  • User-Agent:瀏覽器的用戶代理字符串摔敛。

??雖然不同瀏覽器實際發(fā)送的頭部信息會有所不同廷蓉,但以上列出的基本上是所有瀏覽器都會發(fā)送的。
??使用 setRequestHeader() 方法可以設(shè)置自定義的請求頭部信息马昙。這個方法接受兩個參數(shù):頭部字段的名稱和頭部字段的值桃犬。
??要成功發(fā)送請求頭部信息,必須在調(diào)用 open() 方法之后且調(diào)用 send() 方法之前調(diào)用 setRequestHeader()行楞,如下面的例子所示攒暇。

var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    } 
};
xhr.open("get", "example.php", true);

xhr.setRequestHeader("MyHeader", "MyValue");

xhr.send(null);

??服務(wù)器在接收到這種自定義的頭部信息之后,可以執(zhí)行相應(yīng)的后續(xù)操作敢伸。我們建議讀者使用自定義的頭部字段名稱扯饶,不要使用瀏覽器正常發(fā)送的字段名稱,否則有可能會影響服務(wù)器的響應(yīng)池颈。有的瀏覽器允許開發(fā)人員重寫默認的頭部信息尾序,但有的覽器則不允許這樣做。

??調(diào)用 XHR 對象的 getResponseHeader() 方法并傳入頭部字段名稱躯砰,可以取得相應(yīng)的響應(yīng)頭部信息每币。而調(diào)用 getAllResponseHeaders() 方法則可以取得一個包含所有頭部信息的長字符串。來看下面的例子琢歇。

var myHeader = xhr.getResponseHeader("MyHeader");
var allHeaders = xhr.getAllResponseHeaders();

??在服務(wù)器端兰怠,也可以利用頭部信息向瀏覽器發(fā)送額外的、結(jié)構(gòu)化的數(shù)據(jù)李茫。在沒有自定義信息的情況下揭保,getAllResponseHeaders() 方法通常會返回如下所示的多行文本內(nèi)容:

Date: Sun, 14 Nov 2004 18:04:03 GMT
Server: Apache/1.3.29 (Unix)
Vary: Accept
X-Powered-By: PHP/4.3.8
Connection: close
Content-Type: text/html; charset=iso-8859-1

??這種格式化的輸出可以方便我們檢查響應(yīng)中所有頭部字段的名稱,而不必一個一個地檢查某個字段是否存在魄宏。

1.3秸侣、GET 請求

??GET 是最常見的請求類型,最常用于向服務(wù)器查詢某些信息宠互。必要時味榛,可以將查詢字符串參數(shù)追加到 URL 的末尾,以便將信息發(fā)送給服務(wù)器予跌。
??對 XHR 而言搏色,位于傳入 open() 方法的 URL 末尾的查詢字符串必須經(jīng)過正確的編碼才行。
??使用 GET 請求經(jīng)常會發(fā)生的一個錯誤券册,就是查詢字符串的格式有問題频轿。查詢字符串中每個參數(shù)的名稱和值都必須使用 encodeURIComponent() 進行編碼垂涯,然后才能放到 URL 的末尾;而且所有名-值對兒都必須由和號(&)分隔航邢,如下面的例子所示集币。

xhr.open("get", "example.php?name1=value1&name2=value2", true);

??下面這個函數(shù)可以輔助向現(xiàn)有 URL 的末尾添加查詢字符串參數(shù):

function addURLParam(url, name, value) {
    url += (url.indexOf("?") == -1 ? "?" : "&");
    url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
    return url;
}

??這個 addURLParam() 函數(shù)接受三個參數(shù):要添加參數(shù)的 URL、參數(shù)的名稱和參數(shù)的值翠忠。
??這個函數(shù)首先檢查 URL 是否包含問號(以確定是否已經(jīng)有參數(shù)存在)。如果沒有乞榨,就添加一個問號秽之;否則,就添加一個和號吃既。然后考榨,將參數(shù)名稱和值進行編碼,再添加到 URL 的末尾鹦倚。最后返回添加參數(shù)之后的 URL河质。
??下面是使用這個函數(shù)來構(gòu)建請求 URL 的示例。

var url = "example.php";

// 添加參數(shù)
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");

//初始化請求
xhr.open("get", url,  false);

??在這里使用 addURLParam() 函數(shù)可以確保查詢字符串的格式良好震叙,并可靠地用于 XHR 對象掀鹅。

1.4、POST 請求

??使用頻率僅次于 GET 的是 POST 請求媒楼,通常用于向服務(wù)器發(fā)送應(yīng)該被保存的數(shù)據(jù)乐尊。
??POST 請求應(yīng)該把數(shù)據(jù)作為請求的主體提交,而 GET 請求傳統(tǒng)上不是這樣划址。
??POST 請求的主體可以包含非常多的數(shù)據(jù)扔嵌,而且格式不限。
??在 open() 方法第一個參數(shù)的位置傳入"post"夺颤,就可以初始化一個 POST 請求痢缎,如下面的例子所示。

xhr.open("post", "example.php", true);

??發(fā)送 POST 請求的第二步就是向 send() 方法中傳入某些數(shù)據(jù)世澜。由于 XHR 最初的設(shè)計主要是為了處理 XML独旷,因此可以在此傳入 XML DOM 文檔,傳入的文檔經(jīng)序列化之后將作為請求主體被提交到服務(wù)器宜狐。
??當然势告,也可以在此傳入任何想發(fā)送到服務(wù)器的字符串。

??默認情況下抚恒,服務(wù)器對 POST 請求和提交 Web 表單的請求并不會一視同仁咱台。因此,服務(wù)器端必須有程序來讀取發(fā)送過來的原始數(shù)據(jù)俭驮,并從中解析出有用的部分回溺。
??不過春贸,我們可以使用 XHR 來模仿表單提交:首先將 Content-Type 頭部信息設(shè)置為 application/x-www-form-urlencoded,也就是表單提交時的內(nèi)容類型遗遵,其次是以適當?shù)母袷絼?chuàng)建一個字符串萍恕。POST 數(shù)據(jù)的格式與查詢字符串格式相同。如果需要將頁面中表單的數(shù)據(jù)進行序列化车要,然后再通過 XHR 發(fā)送到服務(wù)器允粤,那么就可以使用 serialize() 函數(shù)來創(chuàng)建這個字符串:

function serialize(form){
    var parts = [],
        field = null,
        i,
        len,
        j,
        optLen,
        option,
        optValue;

    for (i=0, len=form.elements.length; i < len; i++){
        field = form.elements[i];

        switch(field.type){
            case "select-one":
            case "select-multiple":
                if (field.name.length){
                    for (j=0, optLen = field.options.length; j < optLen; j++){ 
                       option = field.options[j];
                        if (option.selected){
                            optValue = "";
                            if (option.hasAttribute){
                                optValue = (option.hasAttribute("value") ?
                                            option.value : option.text);
                            } else {
                                optValue = (option.attributes["value"].specified ?
                                            option.value : option.text);
                            }
                            parts.push(encodeURIComponent(field.name) + "=" +
                                       encodeURIComponent(optValue));
                        }
                    }
                }
                break;

            case undefined: //字段集
            case "file": //文件輸入
            case "submit": //提交按鈕
            case "reset": //重置按鈕
            case "button": //自定義按鈕
                break;

            case "radio": //單選按鈕
            case "checkbox": //復選框
                if (!field.checked){
                    break;
                }
            /* 執(zhí)行默認操作 */
            default:
                //不包含沒有名字的表單字段
                if (field.name.length){
                    parts.push(encodeURIComponent(field.name) + "=" +
                               encodeURIComponent(field.value));
                }
        }
    }
    return parts.join("&");
}

function submitData(){
    var xhr = createXHR();
    xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
 };

 xhr.open("post", "postexample.php", true);
 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

 var form = document.getElementById("user-info");
 xhr.send(serialize(form));
} 

??這個函數(shù)可以將 ID 為"user-info"的表單中的數(shù)據(jù)序列化之后發(fā)送給服務(wù)器。而下面的示例 PHP 文件 postexample.php 就可以通過$_POST 取得提交的數(shù)據(jù)了:

<?php
    header("Content-Type: text/plain");
    echo <<<EOF
Name: {$_POST[‘user-name’]}
Email: {$_POST[‘user-email’]}
EOF;
?>

??如果不設(shè)置 Content-Type 頭部信息翼岁,那么發(fā)送給服務(wù)器的數(shù)據(jù)就不會出現(xiàn)在$_POST 超級全局變量中类垫。這時候,要訪問同樣的數(shù)據(jù)琅坡,就必須借助$HTTP_RAW_POST_DATA悉患。

??與 GET 請求相比,POST 請求消耗的資源會更多一些榆俺。從性能角度來看售躁,以發(fā)送相同的數(shù)據(jù)計,GET 請求的速度最多可達到 POST 請求的兩倍茴晋。

2陪捷、XMLHTtpRequest 2 級

??鑒于 XHR 已經(jīng)得到廣泛接受,成為了事實標準晃跺,W3C 也著手制定相應(yīng)的標準以規(guī)范其行為揩局。
XMLHttpRequest 1 級只是把已有的 XHR 對象的實現(xiàn)細節(jié)描述了出來。而 XMLHttpRequest 2 級則進一步發(fā)展了 XHR掀虎。
??并非所有瀏覽器都完整地實現(xiàn)了 XMLHttpRequest 2 級規(guī)范凌盯,但所有瀏覽器都實現(xiàn)了它規(guī)定的部分內(nèi)容。

2.1烹玉、FormData

??現(xiàn)代 Web 應(yīng)用中頻繁使用的一項功能就是表單數(shù)據(jù)的序列化驰怎,XMLHttpRequest 2 級為此定義了 FormData 類型。
??FormData 為序列化表單以及創(chuàng)建與表單格式相同的數(shù)據(jù)(用于通過 XHR 傳輸)提供了便利二打。下面的代碼創(chuàng)建了一個 FormData 對象县忌,并向其中添加了一些數(shù)據(jù)。

var data = new FormData();
data.append("name", "Nicholas");

??這個 append() 方法接收兩個參數(shù):鍵和值继效,分別對應(yīng)表單字段的名字和字段中包含的值症杏。可以像這樣添加任意多個鍵值對兒瑞信。
??而通過向 FormData 構(gòu)造函數(shù)中傳入表單元素厉颤,也可以用表單元素的數(shù)據(jù)預(yù)先向其中填入鍵值對兒:

var data = new FormData(document.forms[0]);

??創(chuàng)建了 FormData 的實例后,可以將它直接傳給 XHR 的 send() 方法凡简,如下所示:

var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText); 
       } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};

xhr.open("post","postexample.php", true);
var form = document.getElementById("user-info");
xhr.send(new FormData(form));

??使用 FormData 的方便之處體現(xiàn)在不必明確地在 XHR 對象上設(shè)置請求頭部逼友。XHR 對象能夠識別傳入的數(shù)據(jù)類型是 FormData 的實例精肃,并配置適當?shù)念^部信息。

??支持 FormData 的瀏覽器有 Firefox 4+帜乞、Safari 5+司抱、Chrome 和 Android 3+版 WebKit。

2.2黎烈、超時設(shè)定

??IE8 為 XHR 對象添加了一個 timeout 屬性习柠,表示請求在等待響應(yīng)多少毫秒之后就終止。在給 timeout 設(shè)置一個數(shù)值后照棋,如果在規(guī)定的時間內(nèi)瀏覽器還沒有接收到響應(yīng)津畸,那么就會觸發(fā) timeout 事件,進而會調(diào)用 ontimeout 事件處理程序必怜。這項功能后來也被收入了 XMLHttpRequest 2 級規(guī)范中。來看下面的例子后频。

var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        try {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                alert("Request was unsuccessful: " + xhr.status);
            }
        } catch (ex){
            // 假設(shè)由 ontimeout 事件處理程序處理
        }
    }
};

xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; // 將超時設(shè)置為 1 秒鐘(僅適用于 IE8+)
xhr.ontimeout = function(){
    alert("Request did not return in a second.");
};
xhr.send(null);

??這個例子示范了如何使用 timeout 屬性梳庆。將這個屬性設(shè)置為 1000 毫秒,意味著如果請求在 1 秒鐘內(nèi)還沒有返回卑惜,就會自動終止膏执。請求終止時,會調(diào)用 ontimeout 事件處理程序露久。
??但此時 readyState 可能已經(jīng)改變?yōu)?4 了更米,這意味著會調(diào)用 onreadystatechange 事件處理程序。
??可是毫痕,如果在超時終止請求之后再訪問 status 屬性征峦,就會導致錯誤。為避免瀏覽器報告錯誤消请,可以將檢查 status 屬性的語句封裝在一個 try-catch 語句當中栏笆。

2.3、overrideMimeType() 方法

??Firefox 最早引入了 overrideMimeType() 方法臊泰,用于重寫 XHR 響應(yīng)的 MIME 類型蛉加。這個方法后來也被納入了 XMLHttpRequest 2 級規(guī)范。
??因為返回響應(yīng)的 MIME 類型決定了 XHR 對象如何處理它缸逃,所以提供一種方法能夠重寫服務(wù)器返回的 MIME 類型是很有用的针饥。比如,服務(wù)器返回的 MIME 類型是 text/plain需频,但數(shù)據(jù)中實際包含的是 XML丁眼。根據(jù) MIME 類型,即使數(shù)據(jù)是 XML贺辰,responseXML 屬性中仍然是 null户盯。通過調(diào)用 overrideMimeType() 方法嵌施,可以保證把響應(yīng)當作 XML 而非純文本來處理。

var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);

??這個例子強迫 XHR 對象將響應(yīng)當作 XML 而非純文本來處理莽鸭。調(diào)用 overrideMimeType() 必須在 send()方法之前吗伤,才能保證重寫響應(yīng)的 MIME 類型。
??支持 overrideMimeType() 方法的瀏覽器有 Firefox硫眨、Safari 4+足淆、Opera 10.5 和 Chrome。

3礁阁、進度事件

??Progress Events 規(guī)范是 W3C 的一個工作草案巧号,定義了與客戶端服務(wù)器通信有關(guān)的事件。這些事件最早其實只針對 XHR 操作姥闭,但目前也被其他 API 借鑒丹鸿。有以下 6 個進度事件。

  • loadstart:在接收到響應(yīng)數(shù)據(jù)的第一個字節(jié)時觸發(fā)棚品。
  • progress:在接收響應(yīng)期間持續(xù)不斷地觸發(fā)靠欢。
  • error:在請求發(fā)生錯誤時觸發(fā)。
  • abort:在因為調(diào)用 abort() 方法而終止連接時觸發(fā)铜跑。
  • load:在接收到完整的響應(yīng)數(shù)據(jù)時觸發(fā)门怪。
  • loadend:在通信完成或者觸發(fā) error、abort 或 load 事件后觸發(fā)锅纺。

??每個請求都從觸發(fā) loadstart 事件開始掷空,接下來是一或多個 progress 事件,然后觸發(fā) error囤锉、abort 或 load 事件中的一個坦弟,最后以觸發(fā) loadend 事件結(jié)束。
??支持前 5 個事件的瀏覽器有 Firefox 3.5+官地、Safari 4+减拭、Chrome、iOS 版 Safari 和 Android 版 WebKit区丑。Opera(從第 11 版開始)拧粪、IE 8+只支持 load 事件。目前還沒有瀏覽器支持 loadend 事件沧侥。這些事件大都很直觀可霎,但其中兩個事件有一些細節(jié)需要注意。

3.1宴杀、load 事件

??Firefox 在實現(xiàn) XHR 對象的某個版本時癣朗,曾致力于簡化異步交互模型。最終旺罢,F(xiàn)irefox 實現(xiàn)中引入了 load 事件旷余,用以替代 readystatechange 事件绢记。響應(yīng)接收完畢后將觸發(fā) load 事件,因此也就沒有必要去檢查 readyState 屬性了正卧。
??而 onload 事件處理程序會接收到一個 event 對象蠢熄,其 target 屬性就指向 XHR 對象實例,因而可以訪問到 XHR 對象的所有方法和屬性炉旷。
??然而签孔,并非所有瀏覽器都為這個事件實現(xiàn)了適當?shù)氖录ο蟆=Y(jié)果窘行,開發(fā)人員還是要像下面這樣被迫使用 XHR 對象變量饥追。

var xhr = createXHR();
xhr.onload = function(){
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
        alert(xhr.responseText);
    } else {
        alert("Request was unsuccessful: " + xhr.status);
    }
};
xhr.open("get", "altevents.php", true);
xhr.send(null);

??只要瀏覽器接收到服務(wù)器的響應(yīng),不管其狀態(tài)如何罐盔,都會觸發(fā) load 事件但绕。而這意味著你必須要檢查 status 屬性,才能確定數(shù)據(jù)是否真的已經(jīng)可用了惶看。Firefox壁熄、Opera、Chrome 和 Safari 都支持 load 事件碳竟。

3.2、progress 事件

??Mozilla 對 XHR 的另一個革新是添加了 progress 事件狸臣,這個事件會在瀏覽器接收新數(shù)據(jù)期間周期性地觸發(fā)莹桅。
??而 onprogress 事件處理程序會接收到一個 event 對象,其 target 屬性是 XHR 對象烛亦,但包含著三個額外的屬性:lengthComputable诈泼、position 和 totalSize。
??其中煤禽,lengthComputable 是一個表示進度信息是否可用的布爾值铐达,position 表示已經(jīng)接收的字節(jié)數(shù),totalSize 表示根據(jù) Content-Length 響應(yīng)頭部確定的預(yù)期字節(jié)數(shù)檬果。有了這些信息瓮孙,我們就可以為用戶創(chuàng)建一個進度指示器了。下面展示了為用戶創(chuàng)建進度指示器的一個示例选脊。

var xhr = createXHR();
xhr.onload = function(event){
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
        alert(xhr.responseText);
    } else {
        alert("Request was unsuccessful: " + xhr.status);
    }
};

xhr.onprogress = function(event){
    var divStatus = document.getElementById("status");
    if (event.lengthComputable){
        divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize +" bytes";
    }
};

xhr.open("get", "altevents.php", true);
xhr.send(null);

??為確保正常執(zhí)行杭抠,必須在調(diào)用 open() 方法之前添加 onprogress 事件處理程序。在前面的例子中恳啥,每次觸發(fā) progress 事件偏灿,都會以新的狀態(tài)信息更新 HTML 元素的內(nèi)容。如果響應(yīng)頭部中包含 Content-Length 字段钝的,那么也可以利用此信息來計算從響應(yīng)中已經(jīng)接收到的數(shù)據(jù)的百分比翁垂。

4铆遭、跨源資源共享

??通過 XHR 實現(xiàn) Ajax 通信的一個主要限制,來源于跨域安全策略沿猜。默認情況下枚荣,XHR 對象只能訪問與包含它的頁面位于同一個域中的資源。這種安全策略可以預(yù)防某些惡意行為邢疙。
??但是棍弄,實現(xiàn)合理的跨域請求對開發(fā)某些瀏覽器應(yīng)用程序也是至關(guān)重要的。

??CORS(Cross-Origin Resource Sharing疟游,跨源資源共享)是 W3C 的一個工作草案呼畸,定義了在必須訪問跨源資源時,瀏覽器與服務(wù)器應(yīng)該如何溝通颁虐。
??CORS 背后的基本思想蛮原,就是使用自定義的 HTTP 頭部讓瀏覽器與服務(wù)器進行溝通,從而決定請求或響應(yīng)是應(yīng)該成功另绩,還是應(yīng)該失敗儒陨。
??比如一個簡單的使用 GET 或 POST 發(fā)送的請求,它沒有自定義的頭部笋籽,而主體內(nèi)容是 text/plain蹦漠。在發(fā)送該請求時,需要給它附加一個額外的 Origin 頭部,其中包含請求頁面的源信息(協(xié)議、域名和端口)蚀浆,以便服務(wù)器根據(jù)這個頭部信息來決定是否給予響應(yīng)甜攀。下面是 Origin 頭部的一個示例:

Origin: http://www.nczonline.net

??如果服務(wù)器認為這個請求可以接受,就在 Access-Control-Allow-Origin 頭部中回發(fā)相同的源信息(如果是公共資源,可以回發(fā)"*")。例如:

Access-Control-Allow-Origin: http://www.nczonline.net

??如果沒有這個頭部,或者有這個頭部但源信息不匹配棵红,瀏覽器就會駁回請求。正常情況下咧栗,瀏覽器會處理請求逆甜。注意,請求和響應(yīng)都不包含 cookie 信息致板。

4.1忆绰、IE 對 CORS 的實現(xiàn)

??微軟在 IE8 中引入了 XDR(XDomainRequest)類型。這個對象與 XHR 類似可岂,但能實現(xiàn)安全可靠的跨域通信错敢。
??XDR 對象的安全機制部分實現(xiàn)了 W3C 的 CORS 規(guī)范。以下是 XDR 與 XHR 的一些不同之處。

  • cookie 不會隨請求發(fā)送稚茅,也不會隨響應(yīng)返回纸淮。
  • 只能設(shè)置請求頭部信息中的 Content-Type 字段。
  • 不能訪問響應(yīng)頭部信息亚享。
  • 只支持 GET 和 POST 請求咽块。

??這些變化使 CSRF(Cross-Site Request Forgery,跨站點請求偽造)和 XSS(Cross-Site Scripting欺税,跨站點腳本)的問題得到了緩解侈沪。
??被請求的資源可以根據(jù)它認為合適的任意數(shù)據(jù)(用戶代理、來源頁面等)來決定是否設(shè)置 Access-Control- Allow-Origin 頭部晚凿。作為請求的一部分亭罪,Origin 頭部的值表示請求的來源域,以便遠程資源明確地識別 XDR 請求歼秽。
??XDR 對象的使用方法與 XHR 對象非常相似应役。也是創(chuàng)建一個 XDomainRequest 的實例,調(diào)用 open() 方法燥筷,再調(diào)用 send() 方法箩祥。
??但與 XHR 對象的 open() 方法不同,XDR 對象的 open() 方法只接收兩個參數(shù):請求的類型和 URL肆氓。
??所有 XDR 請求都是異步執(zhí)行的袍祖,不能用它來創(chuàng)建同步請求。請求返回之后谢揪,會觸發(fā) load 事件蕉陋,響應(yīng)的數(shù)據(jù)也會保存在 responseText 屬性中,如下所示键耕。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

??在接收到響應(yīng)后,你只能訪問響應(yīng)的原始文本柑营;沒有辦法確定響應(yīng)的狀態(tài)代碼屈雄。而且,只要響應(yīng)有效就會觸發(fā) load 事件官套,如果失斁颇獭(包括響應(yīng)中缺少 Access-Control-Allow-Origin 頭部)就會觸發(fā) error 事件。
??遺憾的是奶赔,除了錯誤本身之外惋嚎,沒有其他信息可用,因此唯一能夠確定的就只有請求未成功了站刑。要檢測錯誤另伍,可以像下面這樣指定一個 onerror 事件處理程序。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

??鑒于導致 XDR 請求失敗的因素很多,因此建議你不要忘記通過 onerror 事件處理程序來捕獲該事件摆尝;否則温艇,即使請求失敗也不會有任何提示。
??在請求返回前調(diào)用 abort() 方法可以終止請求:

xdr.abort(); // 終止請求

??與 XHR 一樣堕汞,XDR 對象也支持 timeout 屬性以及 ontimeout 事件處理程序勺爱。下面是一個例子。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
    alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

??上述例子會在運行 1 秒鐘后超時讯检,并隨即調(diào)用 ontimeout 事件處理程序琐鲁。
??為支持 POST 請求,XDR 對象提供了 contentType 屬性人灼,用來表示發(fā)送數(shù)據(jù)的格式围段,如下面的例子所示。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open("post", "http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");

??這個屬性是通過 XDR 對象影響頭部信息的唯一方式挡毅。

4.2蒜撮、其他瀏覽器對 CORS 的實現(xiàn)

??Firefox 3.5+、Safari 4+跪呈、Chrome段磨、iOS 版 Safari 和 Android 平臺中的 WebKit 都通過 XMLHttpRequest 對象實現(xiàn)了對 CORS 的原生支持。在嘗試打開不同來源的資源時耗绿,無需額外編寫代碼就可以觸發(fā)這個行為苹支。
??要請求位于另一個域中的資源,使用標準的 XHR 對象并在 open() 方法中傳入絕對 URL 即可误阻,例如:

var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);

??與 IE 中的 XDR 對象不同债蜜,通過跨域 XHR 對象可以訪問 status 和 statusText 屬性,而且還支持同步請求究反。
??跨域 XHR 對象也有一些限制寻定,但為了安全這些限制是必需的。以下就是這些限制精耐。

  • 不能使用 setRequestHeader()設(shè)置自定義頭部狼速。
  • 不能發(fā)送和接收 cookie。
  • 調(diào)用 getAllResponseHeaders() 方法總會返回空字符串卦停。

??由于無論同源請求還是跨源請求都使用相同的接口向胡,因此對于本地資源,最好使用相對 URL惊完,在訪問遠程資源時再使用絕對 URL僵芹。這樣做能消除歧義,避免出現(xiàn)限制訪問頭部或本地 cookie 信息等問題小槐。

4.3拇派、Preflighted Requests

??CORS 通過一種叫做 Preflighted Requests 的透明服務(wù)器驗證機制支持開發(fā)人員使用自定義的頭部、GET 或 POST 之外的方法,以及不同類型的主體內(nèi)容攀痊。在使用下列高級選項來發(fā)送請求時桐腌,就會向服務(wù)器發(fā)送一個 Preflight 請求。這種請求使用 OPTIONS 方法苟径,發(fā)送下列頭部案站。

  • Origin:與簡單的請求相同。
  • Access-Control-Request-Method:請求自身使用的方法棘街。
  • Access-Control-Request-Headers:(可選)自定義的頭部信息蟆盐,多個頭部以逗號分隔。

??以下是一個帶有自定義頭部 NCZ 的使用 POST 方法發(fā)送的請求遭殉。

Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

??發(fā)送這個請求后石挂,服務(wù)器可以決定是否允許這種類型的請求。服務(wù)器通過在響應(yīng)中發(fā)送如下頭部與瀏覽器進行溝通险污。

  • Access-Control-Allow-Origin:與簡單的請求相同痹愚。
  • Access-Control-Allow-Methods:允許的方法,多個方法以逗號分隔蛔糯。
  • Access-Control-Allow-Headers:允許的頭部拯腮,多個頭部以逗號分隔。
  • Access-Control-Max-Age:應(yīng)該將這個 Preflight 請求緩存多長時間(以秒表示)蚁飒。

??例如:

Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

??Preflight 請求結(jié)束后动壤,結(jié)果將按照響應(yīng)中指定的時間緩存起來。而為此付出的代價只是第一次發(fā)送這種請求時會多一次 HTTP 請求淮逻。
??支持 Preflight 請求的瀏覽器包括 Firefox 3.5+琼懊、Safari 4+和 Chrome。IE 10 及更早版本都不支持爬早。

4.4哼丈、帶憑據(jù)的請求

??默認情況下,跨源請求不提供憑據(jù)(cookie筛严、HTTP 認證及客戶端 SSL 證明 等 )醉旦。 通 過 將 withCredentials 屬性設(shè)置為 true,可以指定某個請求應(yīng)該發(fā)送憑據(jù)脑漫。如果服務(wù)器接受帶憑據(jù)的請求髓抑,會用下面的 HTTP 頭部來響應(yīng)咙崎。

Access-Control-Allow-Credentials: true

??如果發(fā)送的是帶憑據(jù)的請求优幸,但服務(wù)器的響應(yīng)中沒有包含這個頭部,那么瀏覽器就不會把響應(yīng)交給 JavaScript(于是褪猛,responseText 中將是空字符串网杆,status 的值為 0,而且會調(diào)用 onerror() 事件處理程序)。
??另外碳却,服務(wù)器還可以在 Preflight 響應(yīng)中發(fā)送這個 HTTP 頭部队秩,表示允許源發(fā)送帶憑據(jù)的請求。
??支持 withCredentials 屬性的瀏覽器有 Firefox 3.5+昼浦、Safari 4+和 Chrome馍资。IE 10 及更早版本都不支持。

4.5关噪、跨瀏覽器的 CROS

??即使瀏覽器對 CORS 的支持程度并不都一樣鸟蟹,但所有瀏覽器都支持簡單的(非 Preflight 和不帶憑據(jù)的)請求,因此有必要實現(xiàn)一個跨瀏覽器的方案使兔。
??檢測 XHR 是否支持 CORS 的最簡單方式建钥,就是檢查是否存在 withCredentials 屬性。再結(jié)合檢測 XDomainRequest 對象是否存在虐沥,就可以兼顧所有瀏覽器了熊经。

function createCORSRequest(method, url){
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr){
        xhr.open(method, url, true);
    } else if (typeof XDomainRequest != "undefined"){
        vxhr = new XDomainRequest();
        xhr.open(method, url); 
    } else {
        xhr = null;
    }
    return xhr;
}

var request = createCORSRequest("get", "http://www.somewhere-else.com/page/");
if (request){
    request.onload = function(){
        // 對 request.responseText 進行處理
    };
    request.send();
}

??Firefox、Safari 和 Chrome 中的 XMLHttpRequest 對象與 IE 中的 XDomainRequest 對象類似欲险,都提供了夠用的接口镐依,因此以上模式還是相當有用的。這兩個對象共同的屬性/方法如下盯荤。

  • abort():用于停止正在進行的請求馋吗。
  • onerror:用于替代 onreadystatechange 檢測錯誤。
  • onload:用于替代 onreadystatechange 檢測成功秋秤。
  • responseText:用于取得響應(yīng)內(nèi)容宏粤。
  • send():用于發(fā)送請求。
    ??以上成員都包含在 createCORSRequest() 函數(shù)返回的對象中灼卢,在所有瀏覽器中都能正常使用绍哎。

5、其他跨域技術(shù)

??在 CORS 出現(xiàn)以前鞋真,要實現(xiàn)跨域 Ajax 通信頗費一些周折崇堰。開發(fā)人員想出了一些辦法,利用 DOM 中能夠執(zhí)行跨域請求的功能涩咖,在不依賴 XHR 對象的情況下也能發(fā)送某種請求海诲。
??雖然 CORS 技術(shù)已經(jīng)無處不在,但開發(fā)人員自己發(fā)明的這些技術(shù)仍然被廣泛使用檩互,畢竟這樣不需要修改服務(wù)器端代碼特幔。

5.1、圖像 Ping

??上述第一種跨域請求技術(shù)是使用<img>標簽闸昨。我們知道蚯斯,一個網(wǎng)頁可以從任何網(wǎng)頁中加載圖像薄风,不用擔心跨域不跨域。這也是在線廣告跟蹤瀏覽量的主要方式拍嵌。
??可以動態(tài)地創(chuàng)建圖像遭赂,使用它們的 onload 和 onerror 事件處理程序來確定是否接收到了響應(yīng)。
??動態(tài)創(chuàng)建圖像經(jīng)常用于圖像 Ping横辆。圖像 Ping 是與服務(wù)器進行簡單撇他、單向的跨域通信的一種方式。請求的數(shù)據(jù)是通過查詢字符串形式發(fā)送的狈蚤,而響應(yīng)可以是任意內(nèi)容逆粹,但通常是像素圖或 204 響應(yīng)。
??通過圖像 Ping炫惩,瀏覽器得不到任何具體的數(shù)據(jù)僻弹,但通過偵聽 load 和 error 事件,它能知道響應(yīng)是什么時候接收到的他嚷。來看下面的例子蹋绽。

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas"; 

??這里創(chuàng)建了一個 Image 的實例,然后將 onload 和 onerror 事件處理程序指定為同一個函數(shù)筋蓖。這樣無論是什么響應(yīng)卸耘,只要請求完成,就能得到通知粘咖。請求從設(shè)置 src 屬性那一刻開始蚣抗,而這個例子在請求中發(fā)送了一個 name 參數(shù)。
??圖像 Ping 最常用于跟蹤用戶點擊頁面或動態(tài)廣告曝光次數(shù)瓮下。圖像 Ping 有兩個主要的缺點翰铡,一是只能發(fā)送 GET 請求,二是無法訪問服務(wù)器的響應(yīng)文本讽坏。因此锭魔,圖像 Ping 只能用于瀏覽器與服務(wù)器間的單
向通信。

5.2路呜、JSONP

??JSONP 是 JSON with padding(填充式 JSON 或參數(shù)式 JSON)的簡寫迷捧,是應(yīng)用 JSON 的一種新方法,在后來的 Web 服務(wù)中非常流行胀葱。
??JSONP 看起來與 JSON 差不多漠秋,只不過是被包含在函數(shù)調(diào)用中的 JSON,就像下面這樣抵屿。

callback({ "name": "Nicholas" });

??JSONP 由兩部分組成:回調(diào)函數(shù)和數(shù)據(jù)庆锦。回調(diào)函數(shù)是當響應(yīng)到來時應(yīng)該在頁面中調(diào)用的函數(shù)晌该。
??回調(diào)函數(shù)的名字一般是在請求中指定的肥荔。而數(shù)據(jù)就是傳入回調(diào)函數(shù)中的 JSON 數(shù)據(jù)。下面是一個典型的 JSONP 請求朝群。

http://freegeoip.net/json/?callback=handleResponse

??這個 URL 是在請求一個 JSONP 地理定位服務(wù)燕耿。通過查詢字符串來指定 JSONP 服務(wù)的回調(diào)參數(shù)是很常見的,就像上面的 URL 所示姜胖,這里指定的回調(diào)函數(shù)的名字叫 handleResponse()誉帅。
??JSONP 是通過動態(tài)<script>元素來使用的,使用時可以為 src 屬性指定一個跨域 URL右莱。
??這里的<script>元素與<img>元素類似蚜锨,都有能力不受限制地從其他域加載資源。因為 JSONP 是有效的 JavaScript 代碼慢蜓,所以在請求完成后亚再,即在 JSONP 響應(yīng)加載到頁面中以后,就會立即執(zhí)行晨抡。來看一個例子氛悬。

function handleResponse(response){
    alert("You’re at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
}

var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

??上述例子通過查詢地理定位服務(wù)來顯示你的 IP 地址和位置信息。
??JSONP 之所以在開發(fā)人員中極為流行耘柱,主要原因是它非常簡單易用如捅。與圖像 Ping 相比,它的優(yōu)點在于能夠直接訪問響應(yīng)文本调煎,支持在瀏覽器與服務(wù)器之間雙向通信镜遣。
??不過,JSONP 也有兩點不足士袄。
??首先悲关,JSONP 是從其他域中加載代碼執(zhí)行。如果其他域不安全娄柳,很可能會在響應(yīng)中夾帶一些惡意代碼坚洽,而此時除了完全放棄 JSONP 調(diào)用之外,沒有辦法追究西土。因此在使用不是你自己運維的 Web 服務(wù)時讶舰,一定得保證它安全可靠。
??其次需了,要確定 JSONP 請求是否失敗并不容易跳昼。雖然 HTML5 給<script>元素新增了一個 onerror 事件處理程序,但目前還沒有得到任何瀏覽器支持肋乍。為此鹅颊,開發(fā)人員不得不使用計時器檢測指定時間內(nèi)是否接收到了響應(yīng)。但就算這樣也不能盡如人意墓造,畢竟不是每個用戶上網(wǎng)的速度和帶寬都一樣堪伍。

5.3锚烦、Comet

??Comet 是 Alex Russell( Alex Russell 是著名 JavaScript 框架 Dojo 的創(chuàng)始人。)發(fā)明的一個詞兒帝雇,指的是一種更高級的 Ajax 技術(shù)(經(jīng)常也有人稱為“服務(wù)器推送”)涮俄。
??Ajax 是一種從頁面向服務(wù)器請求數(shù)據(jù)的技術(shù),而 Comet 則是一種服務(wù)器向頁面推送數(shù)據(jù)的技術(shù)尸闸。
??Comet 能夠讓信息近乎實時地被推送到頁面上彻亲,非常適合處理體育比賽的分數(shù)和股票報價。

??有兩種實現(xiàn) Comet 的方式:長輪詢吮廉。

??長輪詢是傳統(tǒng)輪詢(也稱為短輪詢)的一個翻版苞尝,即瀏覽器定時向服務(wù)器發(fā)送請求,看有沒有更新的數(shù)據(jù)宦芦。下圖展示的是短輪詢的時間線宙址。

??長輪詢把短輪詢顛倒了一下。頁面發(fā)起一個到服務(wù)器的請求调卑,然后服務(wù)器一直保持連接打開曼氛,直到有數(shù)據(jù)可發(fā)送。發(fā)送完數(shù)據(jù)之后令野,瀏覽器關(guān)閉連接舀患,隨即又發(fā)起一個到服務(wù)器的新請求。這一過程在頁
面打開期間一直持續(xù)不斷气破。下圖展示了長輪詢的時間線聊浅。

??無論是短輪詢還是長輪詢,瀏覽器都要在接收數(shù)據(jù)之前现使,先發(fā)起對服務(wù)器的連接低匙。兩者最大的區(qū)別在于服務(wù)器如何發(fā)送數(shù)據(jù)。短輪詢是服務(wù)器立即發(fā)送響應(yīng)碳锈,無論數(shù)據(jù)是否有效顽冶,而長輪詢是等待發(fā)送響應(yīng)。

??輪詢的優(yōu)勢是所有瀏覽器都支持售碳,因為使用 XHR 對象和 setTimeout() 就能實現(xiàn)强重。而你要做的就是決定什么時候發(fā)送請求。

??第二種流行的 Comet 實現(xiàn)是** HTTP 流**贸人。流不同于上述兩種輪詢间景,因為它在頁面的整個生命周期內(nèi)只使用一個 HTTP 連接。具體來說艺智,就是瀏覽器向服務(wù)器發(fā)送一個請求倘要,而服務(wù)器保持連接打開,然后周期性地向瀏覽器發(fā)送數(shù)據(jù)十拣。比如封拧,下面這段 PHP 腳本就是采用流實現(xiàn)的服務(wù)器中常見的形式志鹃。

<?php
    $i = 0;
    while(true){ 

       // 輸出一些數(shù)據(jù),然后立即刷新輸出緩存
        echo "Number is $i";
        flush();

        // 等幾秒鐘
        sleep(10);
        $i++;
    }

??所有服務(wù)器端語言都支持打印到輸出緩存然后刷新(將輸出緩存中的內(nèi)容一次性全部發(fā)送到客戶端)的功能泽西。而這正是實現(xiàn) HTTP 流的關(guān)鍵所在曹铃。

??在 Firefox、Safari尝苇、Opera 和 Chrome 中,通過偵聽 readystatechange 事件及檢測 readyState 的值是否為 3埠胖,就可以利用 XHR 對象實現(xiàn) HTTP 流糠溜。
??在上述這些瀏覽器中,隨著不斷從服務(wù)器接收數(shù)據(jù)直撤,readyState 的值會周期性地變?yōu)?3非竿。當 readyState 值變?yōu)?3 時,responseText 屬性中就會保存接收到的所有數(shù)據(jù)谋竖。此時红柱,就需要比較此前接收到的數(shù)據(jù),決定從什么位置開始取得最新的數(shù)據(jù)蓖乘。使用 XHR 對象實現(xiàn) HTTP 流的典型代碼如下所示锤悄。

function createStreamingClient(url, progress, finished){
    var xhr = new XMLHttpRequest(),
        received = 0;

    xhr.open("get", url, true);
    xhr.onreadystatechange = function(){
        var result;

        if (xhr.readyState == 3){

            // 只取得最新數(shù)據(jù)并調(diào)整計數(shù)器
            result = xhr.responseText.substring(received);
            received += result.length;

            // 調(diào)用 progress 回調(diào)函數(shù)
            progress(result);

        } else if (xhr.readyState == 4){
            finished(xhr.responseText);
        }
    };
    xhr.send(null);
    return xhr;
}

var client = createStreamingClient("streaming.php", function(data){
    alert("Received: " + data);
 }, function(data){
    alert("Done!");
 });

??這個 createStreamingClient() 函數(shù)接收三個參數(shù):要連接的 URL、在接收到數(shù)據(jù)時調(diào)用的函數(shù)以及關(guān)閉連接時調(diào)用的函數(shù)嘉抒。
??有時候零聚,當連接關(guān)閉時,很可能還需要重新建立些侍,所以關(guān)注連接什么時候關(guān)閉還是有必要的隶症。
??只要 readystatechange 事件發(fā)生,而且 readyState 值為 3岗宣,就對 responseText 進行分割以取得最新數(shù)據(jù)蚂会。這里的 received 變量用于記錄已經(jīng)處理了多少個字符,每次 readyState 值為 3 時都遞增耗式。
??然后胁住,通過 progress 回調(diào)函數(shù)來處理傳入的新數(shù)據(jù)。而當 readyState 值為 4 時刊咳,則執(zhí)行 finished 回調(diào)函數(shù)措嵌,傳入響應(yīng)返回的全部內(nèi)容。
??雖然這個例子比較簡單芦缰,而且也能在大多數(shù)瀏覽器中正常運行(IE 除外)企巢,但管理 Comet 的連接是很容易出錯的,需要時間不斷改進才能達到完美让蕾。瀏覽器社區(qū)認為 Comet 是未來 Web 的一個重要組成
部分浪规,為了簡化這一技術(shù)或听,又為 Comet 創(chuàng)建了兩個新的接口。

5.4笋婿、服務(wù)器發(fā)送事件

??SSE(Server-Sent Events誉裆,服務(wù)器發(fā)送事件)是圍繞只讀 Comet 交互推出的 API 或者模式。
??SSE API 用于創(chuàng)建到服務(wù)器的單向連接缸濒,服務(wù)器通過這個連接可以發(fā)送任意數(shù)量的數(shù)據(jù)足丢。
??服務(wù)器響應(yīng)的 MIME 類型必須是 text/event-stream,而且是瀏覽器中的 JavaScript API 能解析格式輸出庇配。
??SSE 支持短輪詢斩跌、長輪詢和 HTTP 流,而且能在斷開連接時自動確定何時重新連接捞慌。有了這么簡單實用的 API耀鸦,再實現(xiàn) Comet 就容易多了。
??支持 SSE 的瀏覽器有 Firefox 6+啸澡、Safari 5+袖订、Opera 11+、Chrome 和 iOS 4+版 Safari嗅虏。

1. SSE API

??SSE 的 JavaScript API 與其他傳遞消息的 JavaScript API 很相似洛姑。要預(yù)訂新的事件流,首先要創(chuàng)建一個新的 EventSource 對象皮服,并傳進一個入口點:

var source = new EventSource("myevents.php");

??注意吏口,傳入的 URL 必須與創(chuàng)建對象的頁面同源(相同的 URL 模式、域及端口)冰更。EventSource 的實例有一個 readyState 屬性产徊,值為 0 表示正連接到服務(wù)器,值為 1 表示打開了連接蜀细,值為 2 表示關(guān)閉
了連接舟铜。另外,還有以下三個事件奠衔。

  • open:在建立連接時觸發(fā)谆刨。
  • message:在從服務(wù)器接收到新事件時觸發(fā)。
  • error:在無法建立連接時觸發(fā)归斤。

??就一般的用法而言痊夭,onmessage 事件處理程序也沒有什么特別的。

source.onmessage = function(event){
    var data = event.data;
    // 處理數(shù)據(jù)
};

??服務(wù)器發(fā)回的數(shù)據(jù)以字符串形式保存在 event.data 中脏里。

??默認情況下她我,EventSource 對象會保持與服務(wù)器的活動連接。如果連接斷開,還會重新連接番舆。這就意味著 SSE 適合長輪詢和 HTTP 流酝碳。如果想強制立即斷開連接并且不再重新連接,可以調(diào)用 close() 方法恨狈。

source.close();
2. 事件流

??所謂的服務(wù)器事件會通過一個持久的 HTTP 響應(yīng)發(fā)送疏哗,這個響應(yīng)的 MIME 類型為 text/event-stream。響應(yīng)的格式是純文本禾怠,最簡單的情況是每個數(shù)據(jù)項都帶有前綴 data:返奉,例如:

data: foo

data: bar

data: foo
data: bar

??對以上響應(yīng)而言,事件流中的第一個 message 事件返回的 event.data 值為"foo"吗氏,第二個 message 事件返回的 event.data 值為"bar"芽偏,第三個 message 事件返回的 event.data 值為 "foo\nbar"(注意中間的換行符)。
??對于多個連續(xù)的以 data:開頭的數(shù)據(jù)行牲证,將作為多段數(shù)據(jù)解析哮针,每個值之間以一個換行符分隔关面。只有在包含 data:的數(shù)據(jù)行后面有空行時坦袍,才會觸發(fā) message 事件,因此在服務(wù)器上生成事件流時不能忘了多添加這一行等太。

??通過 id:前綴可以給特定的事件指定一個關(guān)聯(lián)的 ID捂齐,這個 ID 行位于 data:行前面或后面皆可:

data: foo
id: 1

??設(shè)置了 ID 后,EventSource 對象會跟蹤上一次觸發(fā)的事件缩抡。如果連接斷開奠宜,會向服務(wù)器發(fā)送一個包含名為 Last-Event-ID 的特殊 HTTP 頭部的請求,以便服務(wù)器知道下一次該觸發(fā)哪個事件瞻想。在多次連接的事件流中,這種機制可以確保瀏覽器以正確的順序收到連接的數(shù)據(jù)段。

5.5庶灿、Web Sockets

??要說最令人津津樂道的新瀏覽器 API决乎,就得數(shù) Web Sockets 了。Web Sockets 的目標是在一個單獨的持久連接上提供全雙工佃迄、雙向通信泼差。
??在 JavaScript 中創(chuàng)建了 Web Socket 之后,會有一個 HTTP 請求發(fā)送到瀏覽器以發(fā)起連接呵俏。在取得服務(wù)器響應(yīng)后堆缘,建立的連接會使用 HTTP 升級從 HTTP 協(xié)議交換為 Web Socket 協(xié)議。
??也就是說普碎,使用標準的 HTTP 服務(wù)器無法實現(xiàn) Web Sockets吼肥,只有支持這種協(xié)議的專門服務(wù)器才能正常工作。

??由于 Web Sockets 使用了自定義的協(xié)議,所以 URL 模式也略有不同潜沦。未加密的連接不再是 http://萄涯,而是 ws://;加密的連接也不是 https://唆鸡,而是 wss://涝影。
??在使用 Web Socket URL 時,必須帶著這個模式争占,因為將來還有可能支持其他模式燃逻。
??使用自定義協(xié)議而非 HTTP 協(xié)議的好處是,能夠在客戶端和服務(wù)器之間發(fā)送非常少量的數(shù)據(jù)臂痕,而不必擔心 HTTP 那樣字節(jié)級的開銷伯襟。由于傳遞的數(shù)據(jù)包很小,因此 Web Sockets 非常適合移動應(yīng)用握童。畢竟
對移動應(yīng)用而言姆怪,帶寬和網(wǎng)絡(luò)延遲都是關(guān)鍵問題。
??使用自定義協(xié)議的缺點在于澡绩,制定協(xié)議的時間比制定 JavaScript API 的時間還要長稽揭。
??Web Sockets 曾幾度擱淺,就因為不斷有人發(fā)現(xiàn)這個新協(xié)議存在一致性和安全性的問題肥卡。Firefox 4 和 Opera 11 都曾默認啟用 Web Sockets溪掀,但在發(fā)布前夕又禁用了,因為又發(fā)現(xiàn)了安全隱患步鉴。目前支持 Web Sockets 的瀏覽器有 Firefox 6+揪胃、Safari 5+、Chrome 和 iOS 4+版 Safari氛琢。

1. Web Sockets API

??要創(chuàng)建 Web Socket喊递,先實例一個 WebSocket 對象并傳入要連接的 URL:

var socket = new WebSocket("ws://www.example.com/server.php");

??注意,必須給 WebSocket 構(gòu)造函數(shù)傳入絕對 URL阳似。同源策略對 Web Sockets 不適用骚勘,因此可以通過它打開到任何站點的連接。至于是否會與某個域中的頁面通信障般,則完全取決于服務(wù)器调鲸。(通過握手信
息就可以知道請求來自何方。)

??實例化了 WebSocket 對象后挽荡,瀏覽器就會馬上嘗試創(chuàng)建連接藐石。與 XHR 類似,WebSocket 也有一個表示當前狀態(tài)的 readyState 屬性定拟。不過于微,這個屬性的值與 XHR 并不相同逗嫡,而是如下所示。

  • WebSocket.OPENING (0):正在建立連接株依。
  • WebSocket.OPEN (1):已經(jīng)建立連接驱证。
  • WebSocket.CLOSING (2):正在關(guān)閉連接。
  • WebSocket.CLOSE (3):已經(jīng)關(guān)閉連接恋腕。

??WebSocket 沒有 readystatechange 事件抹锄;不過,它有其他事件荠藤,對應(yīng)著不同的狀態(tài)伙单。readyState 的值永遠從 0 開始。要關(guān)閉 Web Socket 連接哈肖,可以在任何時候調(diào)用 close() 方法吻育。

socket.close();

??調(diào)用了 close() 之后,readyState 的值立即變?yōu)?2(正在關(guān)閉)淤井,而在關(guān)閉連接后就會變成 3布疼。

2. 發(fā)送和接收數(shù)據(jù)

??Web Socket 打開之后,就可以通過連接發(fā)送和接收數(shù)據(jù)币狠。要向服務(wù)器發(fā)送數(shù)據(jù)游两,使用 send() 方法并傳入任意字符串,例如:

var socket = new WebSocket("ws://www.example.com/server.php");

socket.send("Hello world!");

??因為 Web Sockets 只能通過連接發(fā)送純文本數(shù)據(jù)总寻,所以對于復雜的數(shù)據(jù)結(jié)構(gòu)器罐,在通過連接發(fā)送之前梢为,必須進行序列化渐行。下面的例子展示了先將數(shù)據(jù)序列化為一個 JSON 字符串,然后再發(fā)送到服務(wù)器:

var message = {
    time: new Date(),
    text: "Hello world!",
    clientId: "asdfp8734rew"
};
socket.send(JSON.stringify(message));

??接下來铸董,服務(wù)器要讀取其中的數(shù)據(jù)祟印,就要解析接收到的 JSON 字符串。
??當服務(wù)器向客戶端發(fā)來消息時粟害,WebSocket 對象就會觸發(fā) message 事件蕴忆。這個 message 事件與其他傳遞消息的協(xié)議類似,也是把返回的數(shù)據(jù)保存在 event.data 屬性中悲幅。

socket.onmessage = function(event){
    var data = event.data;
    // 處理數(shù)據(jù)
};

??與通過 send() 發(fā)送到服務(wù)器的數(shù)據(jù)一樣套鹅,event.data 中返回的數(shù)據(jù)也是字符串。如果你想得到其他格式的數(shù)據(jù)汰具,必須手工解析這些數(shù)據(jù)卓鹿。

3. 其他事件

??WebSocket 對象還有其他三個事件,在連接生命周期的不同階段觸發(fā)留荔。

  • open:在成功建立連接時觸發(fā)吟孙。
  • error:在發(fā)生錯誤時觸發(fā),連接不能持續(xù)。
  • close:在連接關(guān)閉時觸發(fā)杰妓。

??WebSocket 對象不支持 DOM 2 級事件偵聽器藻治,因此必須使用 DOM 0 級語法分別定義每個事件處理程序。

var socket = new WebSocket("ws://www.example.com/server.php");

socket.onopen = function(){
    alert("Connection established.");
};
socket.onerror = function(){
    alert("Connection error.");
};
socket.onclose = function(){
    alert("Connection closed.");
};

??在這三個事件中巷挥,只有 close 事件的 event 對象有額外的信息桩卵。
??close 事件的事件對象有三個額外的屬性:wasClean、code 和 reason倍宾。其中吸占,wasClean 是一個布爾值,表示連接是否已經(jīng)明確地關(guān)
閉凿宾;code 是服務(wù)器返回的數(shù)值狀態(tài)碼矾屯;而 reason 是一個字符串,包含服務(wù)器發(fā)回的消息初厚〖希可以把這些信息顯示給用戶,也可以記錄到日志中以便將來分析产禾。

socket.onclose = function(event){
    console.log("Was clean? " + event.wasClean + " Code=" + event.code + " Reason=" + event.reason);
}; 

5.6排作、SSE 與 Web Sockets

??面對某個具體的用例,在考慮是使用 SSE 還是使用 Web Sockets 時亚情,可以考慮如下幾個因素妄痪。
??首先,你是否有自由度建立和維護 Web Sockets 服務(wù)器楞件?因為 Web Socket 協(xié)議不同于 HTTP衫生,所以現(xiàn)有服務(wù)器不能用于 Web Socket 通信。SSE 倒是通過常規(guī) HTTP 通信土浸,因此現(xiàn)有服務(wù)器就可以滿足需求罪针。
??第二個要考慮的問題是到底需不需要雙向通信。如果用例只需讀取服務(wù)器數(shù)據(jù)(如比賽成績)黄伊,那么 SSE 比較容易實現(xiàn)泪酱。如果用例必須雙向通信(如聊天室),那么 Web Sockets 顯然更好还最。
??別忘了墓阀,在不能選擇 Web Sockets 的情況下,組合 XHR 和 SSE 也是能實現(xiàn)雙向通信的拓轻。

6斯撮、安全

??討論 Ajax 和 Comet 安全的文章可謂連篇累牘,而相關(guān)主題的書也已經(jīng)出了很多本了悦即。大型 Ajax 應(yīng)用程序的安全問題涉及面非常之廣吮成,但我們可以從普遍意義上探討一些基本的問題橱乱。
??首先,可以通過 XHR 訪問的任何 URL 也可以通過瀏覽器或服務(wù)器來訪問粱甫。下面的 URL 就是一個例子泳叠。

/getuserinfo.php?id=23

??如果是向這個 URL 發(fā)送請求,可以想象結(jié)果會返回 ID 為 23 的用戶的某些數(shù)據(jù)茶宵。誰也無法保證別人不會將這個 URL 的用戶 ID 修改為 24危纫、56 或其他值。因此乌庶,getuserinfo.php 文件必須知道請求者是否真的有權(quán)限訪問要請求的數(shù)據(jù)种蝶;否則,你的服務(wù)器就會門戶大開瞒大,任何人的數(shù)據(jù)都可能被泄漏出去螃征。
??對于未被授權(quán)系統(tǒng)有權(quán)訪問某個資源的情況,我們稱之為 CSRF(Cross-Site Request Forgery透敌,跨站點請求偽造)盯滚。未被授權(quán)系統(tǒng)會偽裝自己,讓處理請求的服務(wù)器認為它是合法的酗电。
??受到 CSRF 攻擊的 Ajax 程序有大有小魄藕,攻擊行為既有旨在揭示系統(tǒng)漏洞的惡作劇,也有惡意的數(shù)據(jù)竊取或數(shù)據(jù)銷毀撵术。
??為確保通過 XHR 訪問的 URL 安全背率,通行的做法就是驗證發(fā)送請求者是否有權(quán)限訪問相應(yīng)的資源。有下列幾種方式可供選擇嫩与。

  • 要求以 SSL 連接來訪問可以通過 XHR 請求的資源寝姿。
  • 要求每一次請求都要附帶經(jīng)過相應(yīng)算法計算得到的驗證碼。

??請注意蕴纳,下列措施對防范 CSRF 攻擊不起作用会油。

  • 要求發(fā)送 POST 而不是 GET 請求——很容易改變个粱。
  • 檢查來源 URL 以確定是否可信——來源記錄很容易偽造古毛。
  • 基于 cookie 信息進行驗證——同樣很容易偽造。

??XHR 對象也提供了一些安全機制都许,雖然表面上看可以保證安全稻薇,但實際上卻相當不可靠。
??實際上胶征,前面介紹的 open() 方法還能再接收兩個參數(shù):要隨請求一起發(fā)送的用戶名和密碼塞椎。帶有這兩個參數(shù)的請求可以通過 SSL 發(fā)送給服務(wù)器上的頁面,如下面的例子所示睛低。

xhr.open("get", "example.php", true, "username", "password"); // 不要這樣做0负荨服傍!

??即便可以考慮這種安全機制,但還是盡量不要這樣做骂铁。把用戶名和密碼保存在 JavaScript 代碼中本身就是極為不安全的吹零。任何人,只要他會使用 JavaScript 調(diào)試器拉庵,就可以通過查看相應(yīng)的變量發(fā)現(xiàn)純文本形式的用戶名和密碼灿椅。

小結(jié)

??Ajax 是無需刷新頁面就能夠從服務(wù)器取得數(shù)據(jù)的一種方法。關(guān)于 Ajax钞支,可以從以下幾方面來總結(jié)一下茫蛹。

  • 負責 Ajax 運作的核心對象是 XMLHttpRequest(XHR)對象。
  • XHR 對象由微軟最早在 IE5 中引入烁挟,用于通過 JavaScript 從服務(wù)器取得 XML 數(shù)據(jù)婴洼。
  • 在此之后,F(xiàn)irefox撼嗓、Safari窃蹋、Chrome 和 Opera 都實現(xiàn)了相同的特性,使 XHR 成為了 Web 的一個事實標準静稻。
  • 雖然實現(xiàn)之間存在差異警没,但 XHR 對象的基本用法在不同瀏覽器間還是相對規(guī)范的,因此可以放心地用在 Web 開發(fā)當中振湾。

??同源策略是對 XHR 的一個主要約束杀迹,它為通信設(shè)置了“相同的域、相同的端口押搪、相同的協(xié)議”這一限制树酪。試圖訪問上述限制之外的資源,都會引發(fā)安全錯誤大州,除非采用被認可的跨域解決方案续语。
??這個解決方案叫做 CORS(Cross-Origin Resource Sharing,跨源資源共享)厦画,IE8 通過 XDomainRequest 對象支持 CORS疮茄,其他瀏覽器通過 XHR 對象原生支持 CORS。圖像 Ping 和 JSONP 是另外兩種跨域通信的技術(shù)根暑,但不如 CORS 穩(wěn)妥力试。

??Comet 是對 Ajax 的進一步擴展,讓服務(wù)器幾乎能夠?qū)崟r地向客戶端推送數(shù)據(jù)排嫌。實現(xiàn) Comet 的手段主要有兩個:長輪詢和 HTTP 流畸裳。所有瀏覽器都支持長輪詢,而只有部分瀏覽器原生支持 HTTP 流淳地。

??SSE(Server-Sent Events怖糊,服務(wù)器發(fā)送事件)是一種實現(xiàn) Comet 交互的瀏覽器 API帅容,既支持長輪詢,也支持 HTTP 流伍伤。

??Web Sockets 是一種與服務(wù)器進行全雙工丰嘉、雙向通信的信道。與其他方案不同嚷缭,Web Sockets 不使用 HTTP 協(xié)議饮亏,而使用一種自定義的協(xié)議。這種協(xié)議專門為快速傳輸小數(shù)據(jù)設(shè)計阅爽。雖然要求使用不同的 Web 服務(wù)器路幸,但卻具有速度上的優(yōu)勢。

??各方面對 Ajax 和 Comet 的鼓吹吸引了越來越多的開發(fā)人員學習 JavaScript付翁,人們對 Web 開發(fā)的關(guān)注也再度升溫简肴。與 Ajax 有關(guān)的概念都還相對比較新,這些概念會隨著時間推移繼續(xù)發(fā)展百侧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末砰识,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子佣渴,更是在濱河造成了極大的恐慌辫狼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辛润,死亡現(xiàn)場離奇詭異膨处,居然都是意外死亡,警方通過查閱死者的電腦和手機砂竖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門真椿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乎澄,你說我怎么就攤上這事突硝。” “怎么了置济?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵解恰,是天一觀的道長。 經(jīng)常有香客問我舟肉,道長修噪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任路媚,我火速辦了婚禮,結(jié)果婚禮上樊销,老公的妹妹穿的比我還像新娘整慎。我一直安慰自己脏款,他們只是感情好,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布裤园。 她就那樣靜靜地躺著撤师,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拧揽。 梳的紋絲不亂的頭發(fā)上剃盾,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天,我揣著相機與錄音淤袜,去河邊找鬼痒谴。 笑死,一個胖子當著我的面吹牛铡羡,可吹牛的內(nèi)容都是我干的积蔚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼烦周,長吁一口氣:“原來是場噩夢啊……” “哼尽爆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起读慎,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤漱贱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夭委,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饱亿,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年闰靴,在試婚紗的時候發(fā)現(xiàn)自己被綠了彪笼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚂且,死狀恐怖配猫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杏死,我是刑警寧澤泵肄,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站淑翼,受9級特大地震影響腐巢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玄括,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一冯丙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遭京,春花似錦胃惜、人聲如沸泞莉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲫趁。三九已至,卻和暖如春利虫,著一層夾襖步出監(jiān)牢的瞬間挨厚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工糠惫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疫剃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓寞钥,卻偏偏與公主長得像慌申,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子理郑,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容