JavaScript AJAX

2018-07-24 11:53 更新

目錄

瀏覽器與服務(wù)器之間,采用HTTP協(xié)議通信。用戶在瀏覽器地址欄鍵入一個(gè)網(wǎng)址,或者通過(guò)網(wǎng)頁(yè)表單向服務(wù)器提交內(nèi)容,這時(shí)瀏覽器就會(huì)向服務(wù)器發(fā)出HTTP請(qǐng)求。

1999年,微軟公司發(fā)布IE瀏覽器5.0版,第一次引入新功能:允許JavaScript腳本向服務(wù)器發(fā)起HTTP請(qǐng)求。這個(gè)功能當(dāng)時(shí)并沒(méi)有引起注意,直到2004年Gmail發(fā)布和2005年Google Map發(fā)布,才引起廣泛重視。2005年2月,AJAX這個(gè)詞第一次正式提出,指圍繞這個(gè)功能進(jìn)行開(kāi)發(fā)的一整套做法。從此,AJAX成為腳本發(fā)起HTTP通信的代名詞,W3C也在2006年發(fā)布了它的國(guó)際標(biāo)準(zhǔn)。

具體來(lái)說(shuō),AJAX包括以下幾個(gè)步驟。

  1. 創(chuàng)建AJAX對(duì)象
  2. 發(fā)出HTTP請(qǐng)求
  3. 接收服務(wù)器傳回的數(shù)據(jù)
  4. 更新網(wǎng)頁(yè)數(shù)據(jù)

概括起來(lái),就是一句話,AJAX通過(guò)原生的XMLHttpRequest對(duì)象發(fā)出HTTP請(qǐng)求,得到服務(wù)器返回的數(shù)據(jù)后,再進(jìn)行處理。

AJAX可以是同步請(qǐng)求,也可以是異步請(qǐng)求。但是,大多數(shù)情況下,特指異步請(qǐng)求。因?yàn)橥降腁jax請(qǐng)求,對(duì)瀏覽器有“堵塞效應(yīng)”。

XMLHttpRequest對(duì)象

XMLHttpRequest對(duì)象用來(lái)在瀏覽器與服務(wù)器之間傳送數(shù)據(jù)。

var ajax = new XMLHttpRequest();
ajax.open('GET', 'http://www.example.com/page.php', true);

上面代碼向指定的服務(wù)器網(wǎng)址,發(fā)出GET請(qǐng)求。

然后,AJAX指定回調(diào)函數(shù),監(jiān)聽(tīng)通信狀態(tài)(readyState屬性)的變化。

ajax.onreadystatechange = handleStateChange;

一旦拿到服務(wù)器返回的數(shù)據(jù),AJAX不會(huì)刷新整個(gè)網(wǎng)頁(yè),而是只更新相關(guān)部分,從而不打斷用戶正在做的事情。

注意,AJAX只能向同源網(wǎng)址(協(xié)議、域名、端口都相同)發(fā)出HTTP請(qǐng)求,如果發(fā)出跨源請(qǐng)求,就會(huì)報(bào)錯(cuò)(詳見(jiàn)《同源政策》和《CORS機(jī)制》兩節(jié))。

雖然名字里面有XML,但是實(shí)際上,XMLHttpRequest可以報(bào)送各種數(shù)據(jù),包括字符串和二進(jìn)制,而且除了HTTP,它還支持通過(guò)其他協(xié)議傳送(比如File和FTP)。

下面是XMLHttpRequest對(duì)象的典型用法。

var xhr = new XMLHttpRequest();

// 指定通信過(guò)程中狀態(tài)改變時(shí)的回調(diào)函數(shù)
xhr.onreadystatechange = function(){
  // 通信成功時(shí),狀態(tài)值為4
  if (xhr.readyState === 4){
    if (xhr.status === 200){
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.onerror = function (e) {
  console.error(xhr.statusText);
};

// open方式用于指定HTTP動(dòng)詞、請(qǐng)求的網(wǎng)址、是否異步
xhr.open('GET', '/endpoint', true);

// 發(fā)送HTTP請(qǐng)求
xhr.send(null);

open方法的第三個(gè)參數(shù)是一個(gè)布爾值,表示是否為異步請(qǐng)求。如果設(shè)為false,就表示這個(gè)請(qǐng)求是同步的,下面是一個(gè)例子。

var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);
request.send(null);

if (request.status === 200) {
  console.log(request.responseText);
}

XMLHttpRequest實(shí)例的屬性

readyState

readyState是一個(gè)只讀屬性,用一個(gè)整數(shù)和對(duì)應(yīng)的常量,表示XMLHttpRequest請(qǐng)求當(dāng)前所處的狀態(tài)。

  • 0,對(duì)應(yīng)常量UNSENT,表示XMLHttpRequest實(shí)例已經(jīng)生成,但是open()方法還沒(méi)有被調(diào)用。
  • 1,對(duì)應(yīng)常量OPENED,表示send()方法還沒(méi)有被調(diào)用,仍然可以使用setRequestHeader(),設(shè)定HTTP請(qǐng)求的頭信息。
  • 2,對(duì)應(yīng)常量HEADERS_RECEIVED,表示send()方法已經(jīng)執(zhí)行,并且頭信息和狀態(tài)碼已經(jīng)收到。
  • 3,對(duì)應(yīng)常量LOADING,表示正在接收服務(wù)器傳來(lái)的body部分的數(shù)據(jù),如果responseType屬性是text或者空字符串,responseText就會(huì)包含已經(jīng)收到的部分信息。
  • 4,對(duì)應(yīng)常量DONE,表示服務(wù)器數(shù)據(jù)已經(jīng)完全接收,或者本次接收已經(jīng)失敗了。

在通信過(guò)程中,每當(dāng)發(fā)生狀態(tài)變化的時(shí)候,readyState屬性的值就會(huì)發(fā)生改變。這個(gè)值每一次變化,都會(huì)觸發(fā)readyStateChange事件。

if (ajax.readyState == 4) {
  // Handle the response.
} else {
 // Show the 'Loading...' message or do nothing.
}

上面代碼表示,只有readyState變?yōu)?時(shí),才算確認(rèn)請(qǐng)求已經(jīng)成功,其他值都表示請(qǐng)求還在進(jìn)行中。

onreadystatechange

onreadystatechange屬性指向一個(gè)回調(diào)函數(shù),當(dāng)readystatechange事件發(fā)生的時(shí)候,這個(gè)回調(diào)函數(shù)就會(huì)調(diào)用,并且XMLHttpRequest實(shí)例的readyState屬性也會(huì)發(fā)生變化。

另外,如果使用abort()方法,終止XMLHttpRequest請(qǐng)求,onreadystatechange回調(diào)函數(shù)也會(huì)被調(diào)用。

var xmlhttp = new XMLHttpRequest();
xmlhttp.open( 'GET', 'http://example.com' , true );
xmlhttp.onreadystatechange = function () {
  if ( XMLHttpRequest.DONE != xmlhttp.readyState ) {
    return;
  }
  if ( 200 != xmlhttp.status ) {
    return;
  }
  console.log( xmlhttp.responseText );
};
xmlhttp.send();

response

response屬性為只讀,返回接收到的數(shù)據(jù)體(即body部分)。它的類型可以是ArrayBuffer、Blob、Document、JSON對(duì)象、或者一個(gè)字符串,這由XMLHttpRequest.responseType屬性的值決定。

如果本次請(qǐng)求沒(méi)有成功或者數(shù)據(jù)不完整,該屬性就會(huì)等于null。

responseType

responseType屬性用來(lái)指定服務(wù)器返回?cái)?shù)據(jù)(xhr.response)的類型。

  • ”“:字符串(默認(rèn)值)
  • “arraybuffer”:ArrayBuffer對(duì)象
  • “blob”:Blob對(duì)象
  • “document”:Document對(duì)象
  • “json”:JSON對(duì)象
  • “text”:字符串

text類型適合大多數(shù)情況,而且直接處理文本也比較方便,document類型適合返回XML文檔的情況,blob類型適合讀取二進(jìn)制數(shù)據(jù),比如圖片文件。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    var blob = new Blob([this.response], {type: 'image/png'});
    // 或者
    var blob = oReq.response;
  }
};

xhr.send();

如果將這個(gè)屬性設(shè)為ArrayBuffer,就可以按照數(shù)組的方式處理二進(jìn)制數(shù)據(jù)。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
  var uInt8Array = new Uint8Array(this.response);
  for (var i = 0, len = binStr.length; i < len; ++i) {
  // var byte = uInt8Array[i];
  }
};

xhr.send();

如果將這個(gè)屬性設(shè)為“json”,支持JSON的瀏覽器(Firefox>9,chrome>30),就會(huì)自動(dòng)對(duì)返回?cái)?shù)據(jù)調(diào)用JSON.parse()方法。也就是說(shuō),你從xhr.response屬性(注意,不是xhr.responseText屬性)得到的不是文本,而是一個(gè)JSON對(duì)象。

XHR2支持Ajax的返回類型為文檔,即xhr.responseType=”document” 。這意味著,對(duì)于那些打開(kāi)CORS的網(wǎng)站,我們可以直接用Ajax抓取網(wǎng)頁(yè),然后不用解析HTML字符串,直接對(duì)XHR回應(yīng)進(jìn)行DOM操作。

responseText

responseText屬性返回從服務(wù)器接收到的字符串,該屬性為只讀。如果本次請(qǐng)求沒(méi)有成功或者數(shù)據(jù)不完整,該屬性就會(huì)等于null

如果服務(wù)器返回的數(shù)據(jù)格式是JSON,就可以使用responseText屬性。

var data = ajax.responseText;
data = JSON.parse(data);

responseXML

responseXML屬性返回從服務(wù)器接收到的Document對(duì)象,該屬性為只讀。如果本次請(qǐng)求沒(méi)有成功,或者數(shù)據(jù)不完整,或者不能被解析為XML或HTML,該屬性等于null。

返回的數(shù)據(jù)會(huì)被直接解析為DOM對(duì)象。

/* 返回的XML文件如下
  <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
  <book>
      <chapter id="1">(Re-)Introducing JavaScript</chapter>
      <chapter id="2">JavaScript in Action</chapter>
  </book>
*/

var data = ajax.responseXML;
var chapters = data.getElementsByTagName('chapter');

如果服務(wù)器返回的數(shù)據(jù),沒(méi)有明示Content-Type頭信息等于text/xml,可以使用overrideMimeType()方法,指定XMLHttpRequest對(duì)象將返回的數(shù)據(jù)解析為XML。

status

status屬性為只讀屬性,表示本次請(qǐng)求所得到的HTTP狀態(tài)碼,它是一個(gè)整數(shù)。一般來(lái)說(shuō),如果通信成功的話,這個(gè)狀態(tài)碼是200。

  • 200, OK,訪問(wèn)正常
  • 301, Moved Permanently,永久移動(dòng)
  • 302, Move temporarily,暫時(shí)移動(dòng)
  • 304, Not Modified,未修改
  • 307, Temporary Redirect,暫時(shí)重定向
  • 401, Unauthorized,未授權(quán)
  • 403, Forbidden,禁止訪問(wèn)
  • 404, Not Found,未發(fā)現(xiàn)指定網(wǎng)址
  • 500, Internal Server Error,服務(wù)器發(fā)生錯(cuò)誤

基本上,只有2xx和304的狀態(tài)碼,表示服務(wù)器返回是正常狀態(tài)。

if (ajax.readyState == 4) {
  if ( (ajax.status >= 200 && ajax.status < 300)
    || (ajax.status == 304) ) {
    // Handle the response.
  } else {
    // Status error!
  }
}

statusText

statusText屬性為只讀屬性,返回一個(gè)字符串,表示服務(wù)器發(fā)送的狀態(tài)提示。不同于status屬性,該屬性包含整個(gè)狀態(tài)信息,比如”200 OK“。

timeout

timeout屬性等于一個(gè)整數(shù),表示多少毫秒后,如果請(qǐng)求仍然沒(méi)有得到結(jié)果,就會(huì)自動(dòng)終止。如果該屬性等于0,就表示沒(méi)有時(shí)間限制。

  var xhr = new XMLHttpRequest();
  xhr.ontimeout = function () {
    console.error("The request for " + url + " timed out.");
  };
  xhr.onload = function() {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        callback.apply(xhr, args);
      } else {
        console.error(xhr.statusText);
      }
    }
  };
  xhr.open("GET", url, true);
  xhr.timeout = timeout;
  xhr.send(null);
}

事件監(jiān)聽(tīng)接口

XMLHttpRequest第一版,只能對(duì)onreadystatechange這一個(gè)事件指定回調(diào)函數(shù)。該事件對(duì)所有情況作出響應(yīng)。 XMLHttpRequest第二版允許對(duì)更多的事件指定回調(diào)函數(shù)。

  • onloadstart 請(qǐng)求發(fā)出
  • onprogress 正在發(fā)送和加載數(shù)據(jù)
  • onabort 請(qǐng)求被中止,比如用戶調(diào)用了abort()方法
  • onerror 請(qǐng)求失敗
  • onload 請(qǐng)求成功完成
  • ontimeout 用戶指定的時(shí)限到期,請(qǐng)求還未完成
  • onloadend 請(qǐng)求完成,不管成果或失敗
xhr.onload = function() {
 var responseText = xhr.responseText;
 console.log(responseText);
 // process the response.
};

xhr.onerror = function() {
  console.log('There was an error!');
};

注意,如果發(fā)生網(wǎng)絡(luò)錯(cuò)誤(比如服務(wù)器無(wú)法連通),onerror事件無(wú)法獲取報(bào)錯(cuò)信息,所以只能顯示報(bào)錯(cuò)。

withCredentials

withCredentials屬性是一個(gè)布爾值,表示跨域請(qǐng)求時(shí),用戶信息(比如Cookie和認(rèn)證的HTTP頭信息)是否會(huì)包含在請(qǐng)求之中,默認(rèn)為false。即向example.com發(fā)出跨域請(qǐng)求時(shí),不會(huì)發(fā)送example.com設(shè)置在本機(jī)上的Cookie(如果有的話)。

如果你需要通過(guò)跨域AJAX發(fā)送Cookie,需要打開(kāi)withCredentials。

xhr.withCredentials = true;

為了讓這個(gè)屬性生效,服務(wù)器必須顯式返回Access-Control-Allow-Credentials這個(gè)頭信息。

Access-Control-Allow-Credentials: true

.withCredentials屬性打開(kāi)的話,不僅會(huì)發(fā)送Cookie,還會(huì)設(shè)置遠(yuǎn)程主機(jī)指定的Cookie。注意,此時(shí)你的腳本還是遵守同源政策,無(wú)法 從document.cookie或者HTTP回應(yīng)的頭信息之中,讀取這些Cookie。

XMLHttpRequest實(shí)例的方法

abort()

abort方法用來(lái)終止已經(jīng)發(fā)出的HTTP請(qǐng)求。

ajax.open('GET', 'http://www.example.com/page.php', true);
var ajaxAbortTimer = setTimeout(function() {
  if (ajax) {
    ajax.abort();
    ajax = null;
  }
}, 5000);

上面代碼在發(fā)出5秒之后,終止一個(gè)AJAX請(qǐng)求。

getAllResponseHeaders()

getAllResponseHeaders方法返回服務(wù)器發(fā)來(lái)的所有HTTP頭信息。格式為字符串,每個(gè)頭信息之間使用CRLF分隔,如果沒(méi)有受到服務(wù)器回應(yīng),該屬性返回null

getResponseHeader()

getResponseHeader方法返回HTTP頭信息指定字段的值,如果還沒(méi)有收到服務(wù)器回應(yīng)或者指定字段不存在,則該屬性為null。

function getHeaderTime () {
  console.log(this.getResponseHeader("Last-Modified"));
}

var oReq = new XMLHttpRequest();
oReq.open("HEAD", "yourpage.html");
oReq.onload = getHeaderTime;
oReq.send();

如果有多個(gè)字段同名,則它們的值會(huì)被連接為一個(gè)字符串,每個(gè)字段之間使用“逗號(hào)+空格”分隔。

open()

XMLHttpRequest對(duì)象的open方法用于指定發(fā)送HTTP請(qǐng)求的參數(shù),它的使用格式如下,一共可以接受五個(gè)參數(shù)。

void open(
   string method,
   string url,
   optional boolean async,
   optional string user,
   optional string password
);
  • method:表示HTTP動(dòng)詞,比如“GET”、“POST”、“PUT”和“DELETE”。
  • url: 表示請(qǐng)求發(fā)送的網(wǎng)址。
  • async: 格式為布爾值,默認(rèn)為true,表示請(qǐng)求是否為異步。如果設(shè)為false,則send()方法只有等到收到服務(wù)器返回的結(jié)果,才會(huì)有返回值。
  • user:表示用于認(rèn)證的用戶名,默認(rèn)為空字符串。
  • password:表示用于認(rèn)證的密碼,默認(rèn)為空字符串。

如果對(duì)使用過(guò)open()方法的請(qǐng)求,再次使用這個(gè)方法,等同于調(diào)用abort()。

下面發(fā)送POST請(qǐng)求的例子。

xhr.open('POST', encodeURI('someURL'));
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {};
xhr.send(encodeURI('dataString'));

上面方法中,open方法向指定URL發(fā)出POST請(qǐng)求,send方法送出實(shí)際的數(shù)據(jù)。

下面是一個(gè)同步AJAX請(qǐng)求的例子。

var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);
request.send(null);

if (request.status === 200) {
  console.log(request.responseText);
}

send()

send方法用于實(shí)際發(fā)出HTTP請(qǐng)求。如果不帶參數(shù),就表示HTTP請(qǐng)求只包含頭信息,也就是只有一個(gè)URL,典型例子就是GET請(qǐng)求;如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是POST請(qǐng)求。

ajax.open('GET'
  , 'http://www.example.com/somepage.php?id=' + encodeURIComponent(id)
  , true
);

// 等同于
var data = 'id=' + encodeURIComponent(id));
ajax.open('GET', 'http://www.example.com/somepage.php', true);
ajax.send(data);

上面代碼中,GET請(qǐng)求的參數(shù),可以作為查詢字符串附加在URL后面,也可以作為send方法的參數(shù)。

下面是發(fā)送POST請(qǐng)求的例子。

var data = 'email='
  + encodeURIComponent(email)
  + '&password='
  + encodeURIComponent(password);
ajax.open('POST', 'http://www.example.com/somepage.php', true);
ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
ajax.send(data);

如果請(qǐng)求是異步的(默認(rèn)為異步),該方法在發(fā)出請(qǐng)求后會(huì)立即返回。如果請(qǐng)求為同步,該方法只有等到收到服務(wù)器回應(yīng)后,才會(huì)返回。

注意,所有XMLHttpRequest的監(jiān)聽(tīng)事件,都必須在send()方法調(diào)用之前設(shè)定。

send方法的參數(shù)就是發(fā)送的數(shù)據(jù)。多種格式的數(shù)據(jù),都可以作為它的參數(shù)。

void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);

如果發(fā)送Document數(shù)據(jù),在發(fā)送之前,數(shù)據(jù)會(huì)先被串行化。

發(fā)送二進(jìn)制數(shù)據(jù),最好使用ArrayBufferViewBlob對(duì)象,這使得通過(guò)Ajax上傳文件成為可能。

下面是一個(gè)上傳ArrayBuffer對(duì)象的例子。

function sendArrayBuffer() {
  var xhr = new XMLHttpRequest();
  var uInt8Array = new Uint8Array([1, 2, 3]);

  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(uInt8Array.buffer);
}

FormData類型可以用于構(gòu)造表單數(shù)據(jù)。

var formData = new FormData();

formData.append('username', '張三');
formData.append('email', 'zhangsan@example.com');
formData.append('birthDate', 1940);

var xhr = new XMLHttpRequest();
xhr.open("POST", "/register");
xhr.send(formData);

上面的代碼構(gòu)造了一個(gè)formData對(duì)象,然后使用send方法發(fā)送。它的效果與點(diǎn)擊下面表單的submit按鈕是一樣的。

<form id='registration' name='registration' action='/register'>
    <input type='text' name='username' value='張三'>
    <input type='email' name='email' value='zhangsan@example.com'>
    <input type='number' name='birthDate' value='1940'>
    <input type='submit' onclick='return sendForm(this.form);'>
</form>

FormData也可以將現(xiàn)有表單構(gòu)造生成。

var formElement = document.querySelector("form");
var request = new XMLHttpRequest();
request.open("POST", "submitform.php");
request.send(new FormData(formElement));

FormData對(duì)象還可以對(duì)現(xiàn)有表單添加數(shù)據(jù),這為我們操作表單提供了極大的靈活性。

function sendForm(form) {
    var formData = new FormData(form);
    formData.append('csrf', 'e69a18d7db1286040586e6da1950128c');

    var xhr = new XMLHttpRequest();
    xhr.open('POST', form.action, true);
    xhr.onload = function(e) {
        // ...
    };
    xhr.send(formData);

    return false;
}

var form = document.querySelector('#registration');
sendForm(form);

FormData對(duì)象也能用來(lái)模擬File控件,進(jìn)行文件上傳。

function uploadFiles(url, files) {
  var formData = new FormData();

  for (var i = 0, file; file = files[i]; ++i) {
    formData.append(file.name, file); // 可加入第三個(gè)參數(shù),表示文件名
  }

  var xhr = new XMLHttpRequest();
  xhr.open('POST', url, true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);  // multipart/form-data
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  uploadFiles('/server', this.files);
}, false);

FormData也可以加入JavaScript生成的文件。

// 添加JavaScript生成的文件
var content = '<a id="a"><b id="b">hey!</b></a>';
var blob = new Blob([content], { type: "text/xml"});
formData.append("webmasterfile", blob);

setRequestHeader()

setRequestHeader方法用于設(shè)置HTTP頭信息。該方法必須在open()之后、send()之前調(diào)用。如果該方法多次調(diào)用,設(shè)定同一個(gè)字段,則每一次調(diào)用的值會(huì)被合并成一個(gè)單一的值發(fā)送。

xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));

上面代碼首先設(shè)置頭信息Content-Type,表示發(fā)送JSON格式的數(shù)據(jù);然后設(shè)置Content-Length,表示數(shù)據(jù)長(zhǎng)度;最后發(fā)送JSON數(shù)據(jù)。

overrideMimeType()

該方法用來(lái)指定服務(wù)器返回?cái)?shù)據(jù)的MIME類型。該方法必須在send()之前調(diào)用。

傳統(tǒng)上,如果希望從服務(wù)器取回二進(jìn)制數(shù)據(jù),就要使用這個(gè)方法,人為將數(shù)據(jù)類型偽裝成文本數(shù)據(jù)。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);

// 強(qiáng)制將MIME改為文本類型
xhr.overrideMimeType('text/plain; charset=x-user-defined');

xhr.onreadystatechange = function(e) {
  if (this.readyState == 4 && this.status == 200) {
    var binStr = this.responseText;
    for (var i = 0, len = binStr.length; i < len; ++i) {
      var c = binStr.charCodeAt(i);
      var byte = c & 0xff;  // 去除高位字節(jié),留下低位字節(jié)
    }
  }
};

xhr.send();

上面代碼中,因?yàn)閭骰貋?lái)的是二進(jìn)制數(shù)據(jù),首先用xhr.overrideMimeType方法強(qiáng)制改變它的MIME類型,偽裝成文本數(shù)據(jù)。字符集必需指定為“x-user-defined”,如果是其他字符集,瀏覽器內(nèi)部會(huì)強(qiáng)制轉(zhuǎn)碼,將其保存成UTF-16的形式。字符集“x-user-defined”其實(shí)也會(huì)發(fā)生轉(zhuǎn)碼,瀏覽器會(huì)在每個(gè)字節(jié)前面再加上一個(gè)字節(jié)(0xF700-0xF7ff),因此后面要對(duì)每個(gè)字符進(jìn)行一次與運(yùn)算(&),將高位的8個(gè)位去除,只留下低位的8個(gè)位,由此逐一讀出原文件二進(jìn)制數(shù)據(jù)的每個(gè)字節(jié)。

這種方法很麻煩,在XMLHttpRequest版本升級(jí)以后,一般采用指定responseType的方法。

var xhr = new XMLHttpRequest();
xhr.onload = function(e) {
  var arraybuffer = xhr.response;
  // ...
}
xhr.open("GET", url);
xhr.responseType = "arraybuffer";
xhr.send();

XMLHttpRequest實(shí)例的事件

readyStateChange事件

readyState屬性的值發(fā)生改變,就會(huì)觸發(fā)readyStateChange事件。

我們可以通過(guò)onReadyStateChange屬性,指定這個(gè)事件的回調(diào)函數(shù),對(duì)不同狀態(tài)進(jìn)行不同處理。尤其是當(dāng)狀態(tài)變?yōu)?的時(shí)候,表示通信成功,這時(shí)回調(diào)函數(shù)就可以處理服務(wù)器傳送回來(lái)的數(shù)據(jù)。

progress事件

上傳文件時(shí),XMLHTTPRequest對(duì)象的upload屬性有一個(gè)progress,會(huì)不斷返回上傳的進(jìn)度。

假定網(wǎng)頁(yè)上有一個(gè)progress元素。

<progress min="0" max="100" value="0">0% complete</progress>

文件上傳時(shí),對(duì)upload屬性指定progress事件回調(diào)函數(shù),即可獲得上傳的進(jìn)度。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  // Listen to the upload progress.
  var progressBar = document.querySelector('progress');
  xhr.upload.onprogress = function(e) {
    if (e.lengthComputable) {
      progressBar.value = (e.loaded / e.total) * 100;
      progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
    }
  };

  xhr.send(blobOrFile);
}

upload(new Blob(['hello world'], {type: 'text/plain'}));

load事件、error事件、abort事件

load事件表示服務(wù)器傳來(lái)的數(shù)據(jù)接收完畢,error事件表示請(qǐng)求出錯(cuò),abort事件表示請(qǐng)求被中斷。

var xhr = new XMLHttpRequest();

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

xhr.open();

function updateProgress (oEvent) {
  if (oEvent.lengthComputable) {
    var percentComplete = oEvent.loaded / oEvent.total;
    // ...
  } else {
    // 回應(yīng)的總數(shù)據(jù)量未知,導(dǎo)致無(wú)法計(jì)算百分比
  }
}

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.");
}

loadend事件

abort、loaderror這三個(gè)事件,會(huì)伴隨一個(gè)loadend事件,表示請(qǐng)求結(jié)束,但不知道其是否成功。

req.addEventListener("loadend", loadEnd);

function loadEnd(e) {
  alert("請(qǐng)求結(jié)束(不知道是否成功)");
}

文件上傳

HTML網(wǎng)頁(yè)的<form>元素能夠以四種格式,向服務(wù)器發(fā)送數(shù)據(jù)。

  • 使用POST方法,將enctype屬性設(shè)為application/x-www-form-urlencoded,這是默認(rèn)方法。
<form action="register.php" method="post" onsubmit="AJAXSubmit(this); return false;">
</form>
  • 使用POST方法,將enctype屬性設(shè)為text/plain。
<form action="register.php" method="post" enctype="text/plain" onsubmit="AJAXSubmit(this); return false;">
</form>
  • 使用POST方法,將enctype屬性設(shè)為multipart/form-data。
<form action="register.php" method="post" enctype="multipart/form-data" onsubmit="AJAXSubmit(this); return false;">
</form>
  • 使用GET方法,enctype屬性將被忽略。
<form action="register.php" method="get" onsubmit="AJAXSubmit(this); return false;">
</form>

某個(gè)表單有兩個(gè)字段,分別是foobaz,其中foo字段的值等于barbaz字段的值一個(gè)分為兩行的字符串。上面四種方法,都可以將這個(gè)表單發(fā)送到服務(wù)器。

第一種方法是默認(rèn)方法,POST發(fā)送,Encoding type為application/x-www-form-urlencoded。

Content-Type: application/x-www-form-urlencoded

foo=bar&baz=The+first+line.&#37;0D%0AThe+second+line.%0D%0A

第二種方法是POST發(fā)送,Encoding type為text/plain。

Content-Type: text/plain

foo=bar
baz=The first line.
The second line.

第三種方法是POST發(fā)送,Encoding type為multipart/form-data。

Content-Type: multipart/form-data; boundary=---------------------------314911788813839

-----------------------------314911788813839
Content-Disposition: form-data; name="foo"

bar
-----------------------------314911788813839
Content-Disposition: form-data; name="baz"

The first line.
The second line.

-----------------------------314911788813839--

第四種方法是GET請(qǐng)求。

?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

通常,我們使用file控件實(shí)現(xiàn)文件上傳。

<form id="file-form" action="handler.php" method="POST">
  <input type="file" id="file-select" name="photos[]" multiple/>
  <button type="submit" id="upload-button">上傳</button>
</form>

上面HTML代碼中,file控件的multiple屬性,指定可以一次選擇多個(gè)文件;如果沒(méi)有這個(gè)屬性,則一次只能選擇一個(gè)文件。

file對(duì)象的files屬性,返回一個(gè)FileList對(duì)象,包含了用戶選中的文件。

var fileSelect = document.getElementById('file-select');
var files = fileSelect.files;

然后,新建一個(gè)FormData對(duì)象的實(shí)例,用來(lái)模擬發(fā)送到服務(wù)器的表單數(shù)據(jù),把選中的文件添加到這個(gè)對(duì)象上面。

var formData = new FormData();

for (var i = 0; i < files.length; i++) {
  var file = files[i];

  if (!file.type.match('image.*')) {
    continue;
  }

  formData.append('photos[]', file, file.name);
}

上面代碼中的FormData對(duì)象的append方法,除了可以添加文件,還可以添加二進(jìn)制對(duì)象(Blob)或者字符串。

// Files
formData.append(name, file, filename);

// Blobs
formData.append(name, blob, filename);

// Strings
formData.append(name, value);    

append方法的第一個(gè)參數(shù)是表單的控件名,第二個(gè)參數(shù)是實(shí)際的值,第三個(gè)參數(shù)是可選的,通常是文件名。

最后,使用Ajax方法向服務(wù)器上傳文件。

var xhr = new XMLHttpRequest();

xhr.open('POST', 'handler.php', true);

xhr.onload = function () {
  if (xhr.status !== 200) {
    alert('An error occurred!');
  }
};

xhr.send(formData);

目前,各大瀏覽器(包括IE 10)都支持Ajax上傳文件。

除了使用FormData接口上傳,也可以直接使用File API上傳。


var file = document.getElementById('test-input').files[0];
var xhr = new XMLHttpRequest();

xhr.open('POST', 'myserver/uploads');
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);

可以看到,上面這種寫法比FormData的寫法,要簡(jiǎn)單很多。

Fetch API

基本用法

Ajax操作所用的XMLHttpRequest對(duì)象,已經(jīng)有十多年的歷史,它的API設(shè)計(jì)并不是很好,輸入、輸出、狀態(tài)都在同一個(gè)接口管理,容易寫出非?;靵y的代碼。Fetch API是一種新規(guī)范,用來(lái)取代XMLHttpRequest對(duì)象。它主要有兩個(gè)特點(diǎn),一是接口合理化,Ajax是將所有不同性質(zhì)的接口都放在XHR對(duì)象上,而Fetch是將它們分散在幾個(gè)不同的對(duì)象上,設(shè)計(jì)更合理;二是Fetch操作返回Promise對(duì)象,避免了嵌套的回調(diào)函數(shù)。

下面的代碼檢查瀏覽器是否部署了Fetch API。

if ("fetch" in window){
  // 支持
} else {
  // 不支持
}

下面是一個(gè)Fetch API的簡(jiǎn)單例子。

fetch(url).then(function (response) {
  return response.json();
}).then(function (jsonData) {
  console.log(jsonData);
}).catch(function () {
  console.log('出錯(cuò)了');
});

上面代碼向指定的URL發(fā)出請(qǐng)求,得到回應(yīng)后,將其轉(zhuǎn)為JSON格式,輸出到控制臺(tái)。如果出錯(cuò),則輸出一條提示信息。注意,fetch方法返回的是一個(gè)Promise對(duì)象。

作為比較,XMLHttpRequest的寫法如下。

var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
  console.log(xhr.response);
};
xhr.onerror = function() {
  console.log('出錯(cuò)了');
};
xhr.send();

stream數(shù)據(jù)流

Fetch API最大的特點(diǎn)是,除了返回Promise對(duì)象,還有一點(diǎn)就是數(shù)據(jù)傳送是以數(shù)據(jù)流(stream)的形式進(jìn)行的。對(duì)于大文件,數(shù)據(jù)是一段一段得到的。

response.text().then(function (responseText) {
  console.log(responseText);
}

上面代碼中的text(),其實(shí)就是一個(gè)數(shù)據(jù)流讀取器,并使用指定格式解讀。

Fetch API提供以下五個(gè)數(shù)據(jù)流讀取器。

  • .text():返回字符串
  • .json():返回一個(gè)JSON對(duì)象
  • .formData():返回一個(gè)FormData對(duì)象
  • .blob():返回一個(gè)blob對(duì)象
  • .arrayBuffer():返回一個(gè)二進(jìn)制數(shù)組

數(shù)據(jù)流只能讀取一次,一旦讀取,數(shù)據(jù)流就空了。再次讀取就不會(huì)得到結(jié)果。解決方法是在讀取之前,先使用.clone()方法,復(fù)制一份一模一樣的副本。

var url = 'LargeFile.txt';
var progress = 0;
var contentLength = 0;

fetch(url).then(function (response) {
  // 本次請(qǐng)求總的數(shù)據(jù)長(zhǎng)度
  contentLength = response.headers.get('Content-Length');
  var getStream = function (reader) {};
  return getStream(response.body.getReader());
})
.catch(function (error) {
  console.log(error);
});

上面代碼中,response.body.getReader()返回的就是數(shù)據(jù)流之中的一段。處理數(shù)據(jù)流的getStream函數(shù)代碼如下。

var progress = 0;
var contentLength = 0;

var getStream = function (reader) {
  return reader.read().then(function (result) {
    // 如果數(shù)據(jù)已經(jīng)讀取完畢,直接返回
    if (result.done) {
      return;
    }

    // 取出本段數(shù)據(jù)(二進(jìn)制格式)
    var chunk = result.value;

    var text = '';
    // 假定數(shù)據(jù)是UTF-8編碼,前三字節(jié)是數(shù)據(jù)頭,
    // 而且每個(gè)字符占據(jù)一個(gè)字節(jié)(即都為英文字符)
    for (var i = 3; i < chunk.byteLength; i++) {
      text += String.fromCharCode(chunk[i]);
    }

    // 將本段數(shù)據(jù)追加到網(wǎng)頁(yè)之中
    document.getElementById('content').innerHTML += text;

    // 計(jì)算當(dāng)前進(jìn)度
    progress += chunk.byteLength;
    console.log(((progress / contentLength) * 100) + '%');

    // 遞歸處理下一段數(shù)據(jù)
    return getStream(reader);
  };
};

上面這樣的數(shù)據(jù)流處理,可以提高網(wǎng)站性能表現(xiàn),減少內(nèi)存占用,對(duì)于請(qǐng)求大文件或者網(wǎng)速慢的場(chǎng)景相當(dāng)有用。傳統(tǒng)的XMLHTTPRequest對(duì)象不支持?jǐn)?shù)據(jù)流,所有的數(shù)據(jù)必須放在緩存里,等到全部拿到后,再一次性吐出來(lái)。

fetch()

fetch方法的第一個(gè)參數(shù)可以是URL字符串,也可以是后文要講到的Request對(duì)象實(shí)例。Fetch方法返回一個(gè)Promise對(duì)象,并將一個(gè)response對(duì)象傳給回調(diào)函數(shù)。

response對(duì)象有一個(gè)ok屬性,如果返回的狀態(tài)碼在200到299之間(即請(qǐng)求成功),這個(gè)屬性為true,否則為false。因此,判斷請(qǐng)求是否成功的代碼可以寫成下面這樣。

fetch('./api/some.json').then(function (response) {
  if (response.ok) {
    response.json().then(function (data) {
      console.log(data);
    });
  } else {
    console.log('請(qǐng)求失敗,狀態(tài)碼為', response.status);
  }
}, function(err) {
  console.log('出錯(cuò):', err);
});

response對(duì)象除了json方法,還包含了服務(wù)器HTTP回應(yīng)的元數(shù)據(jù)。

fetch('users.json').then(function(response) {
  console.log(response.headers.get('Content-Type'));
  console.log(response.headers.get('Date'));
  console.log(response.status);
  console.log(response.statusText);
  console.log(response.type);
  console.log(response.url);
});

上面代碼中,response對(duì)象有很多屬性,其中的response.type屬性比較特別,表示HTTP回應(yīng)的類型,它有以下三個(gè)值。

  • basic:正常的同域請(qǐng)求
  • cors:CORS機(jī)制下的跨域請(qǐng)求
  • opaque:非CORS機(jī)制下的跨域請(qǐng)求,這時(shí)無(wú)法讀取返回的數(shù)據(jù),也無(wú)法判斷是否請(qǐng)求成功

如果需要在CORS機(jī)制下發(fā)出跨域請(qǐng)求,需要指明狀態(tài)。

fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
  .then(function(response) {
    return response.text();
  })
  .then(function(text) {
    console.log('Request successful', text);
  })
  .catch(function(error) {
    log('Request failed', error)
  });

除了指定模式,fetch方法的第二個(gè)參數(shù)還可以用來(lái)配置其他值,比如指定cookie連同HTTP請(qǐng)求一起發(fā)出。

fetch(url, {
  credentials: 'include'
})

發(fā)出POST請(qǐng)求的寫法如下。

fetch("http://www.example.org/submit.php", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  body: "firstName=Nikhil&favColor=blue&password=easytoguess"
}).then(function(res) {
  if (res.ok) {
    console.log("Perfect! Your settings are saved.");
  } else if (res.status == 401) {
    console.log("Oops! You are not authorized.");
  }
}, function(e) {
  console.log("Error submitting form!");
});

目前,還有一些XMLHttpRequest對(duì)象可以做到,但是Fetch API還沒(méi)做到的地方,比如中途中斷HTTP請(qǐng)求,以及獲取HTTP請(qǐng)求的進(jìn)度。這些不足與Fetch返回的是Promise對(duì)象有關(guān)。

Headers

Fetch API引入三個(gè)新的對(duì)象(也是構(gòu)造函數(shù)):Headers, RequestResponse。其中,Headers對(duì)象用來(lái)構(gòu)造/讀取HTTP數(shù)據(jù)包的頭信息。

var content = 'Hello World';
var headers = new Headers();
headers.append("Accept", "application/json");
headers.append("Content-Type", "text/plain");
headers.append("Content-Length", content.length.toString());
headers.append("X-Custom-Header", "ProcessThisImmediately");

Headers對(duì)象的實(shí)例,除了使用append方法添加屬性,也可以直接通過(guò)構(gòu)造函數(shù)一次性生成。

reqHeaders = new Headers({
  "Content-Type": "text/plain",
  "Content-Length": content.length.toString(),
  "X-Custom-Header": "ProcessThisImmediately",
});

Headers對(duì)象實(shí)例還提供了一些工具方法。

reqHeaders.has("Content-Type") // true
reqHeaders.has("Set-Cookie") // false
reqHeaders.set("Content-Type", "text/html")
reqHeaders.append("X-Custom-Header", "AnotherValue")

reqHeaders.get("Content-Length") // 11
reqHeaders.getAll("X-Custom-Header") // ["ProcessThisImmediately", "AnotherValue"]

reqHeaders.delete("X-Custom-Header")
reqHeaders.getAll("X-Custom-Header") // []

生成Header實(shí)例以后,可以將它作為第二個(gè)參數(shù),傳入Request方法。

var headers = new Headers();
headers.append('Accept', 'application/json');
var request = new Request(URL, {headers: headers});

fetch(request).then(function(response) {
  console.log(response.headers);
});

同樣地,Headers實(shí)例可以用來(lái)構(gòu)造Response方法。

var headers = new Headers({
  'Content-Type': 'application/json',
  'Cache-Control': 'max-age=3600'
});

var response = new Response(
  JSON.stringify({photos: {photo: []}}),
  {'status': 200, headers: headers}
);

response.json().then(function(json) {
  insertPhotos(json);
});

上面代碼中,構(gòu)造了一個(gè)HTTP回應(yīng)。目前,瀏覽器構(gòu)造HTTP回應(yīng)沒(méi)有太大用處,但是隨著Service Worker的部署,不久瀏覽器就可以向Service Worker發(fā)出HTTP回應(yīng)。

Request對(duì)象

Request對(duì)象用來(lái)構(gòu)造HTTP請(qǐng)求。

var req = new Request("/index.html");
req.method // "GET"
req.url // "http://example.com/index.html"

Request對(duì)象的第二個(gè)參數(shù),表示配置對(duì)象。

var uploadReq = new Request("/uploadImage", {
  method: "POST",
  headers: {
    "Content-Type": "image/png",
  },
  body: "image data"
});

上面代碼指定Request對(duì)象使用POST方法發(fā)出,并指定HTTP頭信息和信息體。

下面是另一個(gè)例子。

var req = new Request(URL, {method: 'GET', cache: 'reload'});
fetch(req).then(function(response) {
  return response.json();
}).then(function(json) {
  someOperator(json);
});

上面代碼中,指定請(qǐng)求方法為GET,并且要求瀏覽器不得緩存response。

Request對(duì)象實(shí)例有兩個(gè)屬性是只讀的,不能手動(dòng)設(shè)置。一個(gè)是referrer屬性,表示請(qǐng)求的來(lái)源,由瀏覽器設(shè)置,有可能是空字符串。另一個(gè)是context屬性,表示請(qǐng)求發(fā)出的上下文,如果是image,表示是從img標(biāo)簽發(fā)出,如果是worker,表示是從worker腳本發(fā)出,如果是fetch,表示是從fetch函數(shù)發(fā)出的。

Request對(duì)象實(shí)例的mode屬性,用來(lái)設(shè)置是否跨域,合法的值有以下三種:same-origin、no-cors(默認(rèn)值)、cors。當(dāng)設(shè)置為same-origin時(shí),只能向同域的URL發(fā)出請(qǐng)求,否則會(huì)報(bào)錯(cuò)。

var arbitraryUrl = document.getElementById("url-input").value;
fetch(arbitraryUrl, { mode: "same-origin" }).then(function(res) {
  console.log("Response succeeded?", res.ok);
}, function(e) {
  console.log("Please enter a same-origin URL!");
});

上面代碼中,如果用戶輸入的URL不是同域的,將會(huì)報(bào)錯(cuò),否則就會(huì)發(fā)出請(qǐng)求。

如果mode屬性為no-cors,就與默認(rèn)的瀏覽器行為沒(méi)有不同,類似script標(biāo)簽加載外部腳本文件、img標(biāo)簽加載外部圖片。如果mode屬性為cors,就可以向部署了CORS機(jī)制的服務(wù)器,發(fā)出跨域請(qǐng)求。

var u = new URLSearchParams();
u.append('method', 'flickr.interestingness.getList');
u.append('api_key', '<insert api key here>');
u.append('format', 'json');
u.append('nojsoncallback', '1');

var apiCall = fetch('https://api.flickr.com/services/rest?' + u);

apiCall.then(function(response) {
  return response.json().then(function(json) {
    // photo is a list of photos.
    return json.photos.photo;
  });
}).then(function(photos) {
  photos.forEach(function(photo) {
    console.log(photo.title);
  });
});

上面代碼是向Flickr API發(fā)出圖片請(qǐng)求的例子。

Request對(duì)象的一個(gè)很有用的功能,是在其他Request實(shí)例的基礎(chǔ)上,生成新的Request實(shí)例。

var postReq = new Request(req, {method: 'POST'});

Response

fetch方法返回Response對(duì)象實(shí)例,它有以下屬性。

  • status:整數(shù)值,表示狀態(tài)碼(比如200)
  • statusText:字符串,表示狀態(tài)信息,默認(rèn)是“OK”
  • ok:布爾值,表示狀態(tài)碼是否在200-299的范圍內(nèi)
  • headers:Headers對(duì)象,表示HTTP回應(yīng)的頭信息
  • url:字符串,表示HTTP請(qǐng)求的網(wǎng)址
  • type:字符串,合法的值有五個(gè)basic、cors、default、error、opaque。basic表示正常的同域請(qǐng)求;cors表示CORS機(jī)制的跨域請(qǐng)求;error表示網(wǎng)絡(luò)出錯(cuò),無(wú)法取得信息,status屬性為0,headers屬性為空,并且導(dǎo)致fetch函數(shù)返回Promise對(duì)象被拒絕;opaque表示非CORS機(jī)制的跨域請(qǐng)求,受到嚴(yán)格限制。

Response對(duì)象還有兩個(gè)靜態(tài)方法。

  • Response.error() 返回一個(gè)type屬性為error的Response對(duì)象實(shí)例
  • Response.redirect(url, status) 返回的Response對(duì)象實(shí)例會(huì)重定向到另一個(gè)URL
fetch("https://example.com", init)
.then(function (response) {
// Check that the response is a 200
  if (response.status === 200) {
    alert("Content type: " + response.headers.get('Content-Type'));
  }
});

body屬性

Request對(duì)象和Response對(duì)象都有body屬性,表示請(qǐng)求的內(nèi)容。body屬性可能是以下的數(shù)據(jù)類型。

  • ArrayBuffer
  • ArrayBufferView (Uint8Array等)
  • Blob/File
  • string
  • URLSearchParams
  • FormData
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
  method: "POST",
  body: form
})

上面代碼中,Request對(duì)象的body屬性為表單數(shù)據(jù)。

Request對(duì)象和Response對(duì)象都提供以下方法,用來(lái)讀取body。

  • arrayBuffer()
  • blob()
  • json()
  • text()
  • formData()

注意,上面這些方法都只能使用一次,第二次使用就會(huì)報(bào)錯(cuò),也就是說(shuō),body屬性只能讀取一次。Request對(duì)象和Response對(duì)象都有bodyUsed屬性,返回一個(gè)布爾值,表示body是否被讀取過(guò)。

var res = new Response("one time use");
console.log(res.bodyUsed); // false
res.text().then(function(v) {
  console.log(res.bodyUsed); // true
});
console.log(res.bodyUsed); // true

res.text().catch(function(e) {
  console.log("Tried to read already consumed Response");
});

上面代碼中,第二次通過(guò)text方法讀取Response對(duì)象實(shí)例的body時(shí),就會(huì)報(bào)錯(cuò)。

這是因?yàn)閎ody屬性是一個(gè)stream對(duì)象,數(shù)據(jù)只能單向傳送一次。這樣的設(shè)計(jì)是為了允許JavaScript處理視頻、音頻這樣的大型文件。

如果希望多次使用body屬性,可以使用Response對(duì)象和Request對(duì)象的clone方法。它必須在body還沒(méi)有讀取前調(diào)用,返回一個(gè)新的body,也就是說(shuō),需要使用幾次body,就要調(diào)用幾次clone方法。

addEventListener('fetch', function(evt) {
  var sheep = new Response("Dolly");
  console.log(sheep.bodyUsed); // false
  var clone = sheep.clone();
  console.log(clone.bodyUsed); // false

  clone.text();
  console.log(sheep.bodyUsed); // false
  console.log(clone.bodyUsed); // true

  evt.respondWith(cache.add(sheep.clone()).then(function(e) {
    return sheep;
  });
});

參考鏈接

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)